├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.binaries.txt ├── LICENSE.code.txt ├── README.md ├── bootstrap ├── .gitignore ├── dub.json └── source │ ├── dls │ ├── bootstrap.d │ └── util │ │ ├── getopt.d │ │ └── setup.d │ └── main.d ├── data ├── .gitignore └── data.d ├── dscanner.ini ├── dub.json ├── dub.selections.json ├── i18n ├── data │ ├── translations.d │ └── translations.json ├── dub.json └── source │ └── dls │ └── util │ └── i18n │ ├── constants.d │ └── package.d ├── protocol ├── dub.json └── source │ └── dls │ └── protocol │ ├── definitions.d │ ├── errors.d │ └── interfaces │ ├── client.d │ ├── general.d │ ├── package.d │ ├── text_document.d │ ├── window.d │ └── workspace.d ├── resources └── icon.svg ├── source ├── dls │ ├── info.d │ ├── protocol │ │ ├── handlers.d │ │ ├── interfaces │ │ │ ├── dls.d │ │ │ └── other.d │ │ ├── jsonrpc.d │ │ ├── logger.d │ │ ├── messages │ │ │ ├── client.d │ │ │ ├── general.d │ │ │ ├── methods.d │ │ │ ├── other.d │ │ │ ├── text_document.d │ │ │ ├── window.d │ │ │ └── workspace.d │ │ └── state.d │ ├── server.d │ ├── tools │ │ ├── analysis_tool.d │ │ ├── command_tool.d │ │ ├── configuration.d │ │ ├── format_tool │ │ │ ├── internal │ │ │ │ ├── builtin_format_tool.d │ │ │ │ ├── builtin_format_visitor.d │ │ │ │ ├── dfmt_format_tool.d │ │ │ │ └── format_tool.d │ │ │ └── package.d │ │ ├── symbol_tool │ │ │ ├── internal │ │ │ │ ├── symbol_tool.d │ │ │ │ └── symbol_visitor.d │ │ │ └── package.d │ │ └── tool.d │ └── updater.d └── main.d ├── tests ├── .gitattributes ├── .gitignore ├── formatting │ ├── dub.json │ ├── messages │ │ ├── _order.txt │ │ ├── initialize.in.json │ │ ├── initialize.ref.json │ │ ├── initialized.in.json │ │ ├── initialized.ref.json │ │ ├── shutdown.in.json │ │ ├── shutdown.ref.json │ │ ├── textDocument_formatting.in.json │ │ ├── textDocument_formatting.ref.json │ │ ├── textDocument_onTypeFormatting.in.json │ │ ├── textDocument_onTypeFormatting.ref.json │ │ ├── textDocument_rangeFormatting.in.json │ │ └── textDocument_rangeFormatting.ref.json │ └── source │ │ └── app.d ├── hello │ ├── dub.json │ ├── messages │ │ ├── _order.txt │ │ ├── initialize.in.json │ │ ├── initialize.ref.json │ │ ├── initialized.in.json │ │ ├── initialized.ref.json │ │ ├── shutdown.in.json │ │ └── shutdown.ref.json │ └── source │ │ └── app.d ├── main.d ├── references │ ├── dub.json │ ├── messages │ │ ├── _order.txt │ │ ├── initialize.in.json │ │ ├── initialize.ref.json │ │ ├── initialized.in.json │ │ ├── initialized.ref.json │ │ ├── shutdown.in.json │ │ ├── shutdown.ref.json │ │ ├── textDocument_references_1.in.json │ │ ├── textDocument_references_1.ref.json │ │ ├── textDocument_references_1_noDeclaration.in.json │ │ ├── textDocument_references_1_noDeclaration.ref.json │ │ ├── textDocument_references_2.in.json │ │ ├── textDocument_references_2.ref.json │ │ ├── textDocument_references_2_noDeclaration.in.json │ │ ├── textDocument_references_2_noDeclaration.ref.json │ │ ├── textDocument_references_3.in.json │ │ ├── textDocument_references_3.ref.json │ │ ├── textDocument_references_3_noDeclaration.in.json │ │ ├── textDocument_references_3_noDeclaration.ref.json │ │ ├── textDocument_references_4.in.json │ │ ├── textDocument_references_4.ref.json │ │ ├── textDocument_references_4_noDeclaration.in.json │ │ └── textDocument_references_4_noDeclaration.ref.json │ └── source │ │ └── app.d └── symbol │ ├── dscanner.ini │ ├── dub.json │ ├── messages │ ├── _order.txt │ ├── initialize-documentSymbol.in.json │ ├── initialize-documentSymbol.ref.json │ ├── initialize-symbolInformation.in.json │ ├── initialize-symbolInformation.ref.json │ ├── initialized.in.json │ ├── initialized.ref.json │ ├── shutdown.in.json │ ├── shutdown.ref.json │ ├── textDocument_documentSymbol-documentSymbol.in.json │ ├── textDocument_documentSymbol-documentSymbol.ref.json │ ├── textDocument_documentSymbol-symbolInformation.in.json │ ├── textDocument_documentSymbol-symbolInformation.ref.json │ ├── workspace_symbol.in.json │ └── workspace_symbol.ref.json │ └── source │ ├── app.d │ └── lib.d └── util ├── dub.json └── source └── dls └── util ├── communicator.d ├── disposable_fiber.d ├── document.d ├── json.d └── uri.d /.appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: x64 2 | image: Visual Studio 2019 3 | environment: 4 | matrix: 5 | - ARCH: x86_64 6 | VC_ARCH: x64 7 | cache: 8 | - '%LOCALAPPDATA%\dub -> dub.selections.json' 9 | install: 10 | - ps: $latest = (Invoke-WebRequest "https://ldc-developers.github.io/LATEST").toString().trim() 11 | - ps: $url = "https://github.com/ldc-developers/ldc/releases/download/v$($latest)/ldc2-$($latest)-windows-$($env:VC_ARCH).7z"; 12 | - ps: Push-Location "$($env:TMP)" 13 | - ps: Invoke-WebRequest $url -OutFile ".\ldc.7z" 14 | - ps: 7z x ldc.7z > $null 15 | - ps: Pop-Location 16 | - ps: $env:PATH += ";$($env:TMP)\ldc2-$($latest)-windows-$($env:VC_ARCH)\bin" 17 | - call msvcEnv %VC_ARCH% 18 | build_script: 19 | - if defined APPVEYOR_REPO_TAG_NAME (set BUILD=release) else (set BUILD=debug) 20 | - dub build --arch=%ARCH% --compiler=ldc2 --build=%BUILD% 21 | after_build: 22 | - if defined APPVEYOR_REPO_TAG_NAME (set TAG_NAME=%APPVEYOR_REPO_TAG_NAME%) else (set TAG_NAME=untagged) 23 | - 7z -mx=9 a dls-%TAG_NAME%.windows.%ARCH%.zip dls.exe LICENSE.txt 24 | test_script: 25 | - dub test --arch=%ARCH% --compiler=ldc2 --main-file=tests\main.d 26 | artifacts: 27 | - path: dls-*.zip 28 | deploy: 29 | - provider: GitHub 30 | auth_token: $(GITHUB_API_KEY) 31 | artifact: /dls-.*\.zip/ 32 | on: 33 | appveyor_repo_tag: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/d 3 | 4 | ### D ### 5 | # Compiled Object files 6 | *.o 7 | *.obj 8 | 9 | # Compiled Dynamic libraries 10 | *.so 11 | *.dylib 12 | *.dll 13 | 14 | # Compiled Static libraries 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | 21 | # DUB 22 | .dub 23 | docs.json 24 | __dummy.html 25 | docs/ 26 | 27 | # Code coverage 28 | *.lst 29 | 30 | 31 | # End of https://www.gitignore.io/api/d 32 | 33 | /dls 34 | /dls-test-library 35 | /resources/icon.png 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: xenial 3 | language: d 4 | matrix: 5 | fast_finish: true 6 | include: 7 | - d: ldc 8 | os: linux 9 | env: ARCH=x86_64 10 | - d: ldc 11 | os: osx 12 | env: ARCH=x86_64 13 | - d: dmd-beta 14 | os: linux 15 | - d: ldc-beta 16 | os: linux 17 | allow_failures: 18 | - d: dmd-beta 19 | - d: ldc-beta 20 | cache: 21 | directories: 22 | - $HOME/.dub 23 | before_script: 24 | - ln -s /usr/bin/ld.gold ~/ld 25 | script: 26 | - if [[ -n $TRAVIS_TAG ]]; then export BUILD=release; export OP=build; fi 27 | - export PATH="$HOME:$PATH" 28 | - dub ${OP:-test --main-file=tests/main.d} --arch=${ARCH:-x86_64} --compiler=$DC --build=${BUILD:-debug} 29 | before_deploy: 30 | - if which strip; then strip dls; fi 31 | - zip -9 dls-${TRAVIS_TAG:-untagged}.$TRAVIS_OS_NAME.$ARCH.zip dls LICENSE.txt 32 | - export TARGET_COMMITISH="$(echo -n release/$TRAVIS_TAG | sed 's/.[0-9]*$/.x/')" 33 | deploy: 34 | provider: releases 35 | api_key: "$GITHUB_API_KEY" 36 | target_commitish: "$TARGET_COMMITISH" 37 | file_glob: true 38 | file: dls-*.zip 39 | skip_cleanup: true 40 | on: 41 | condition: "-n $ARCH" 42 | repo: d-language-server/dls 43 | tags: true 44 | -------------------------------------------------------------------------------- /LICENSE.code.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2021 Laurent Tréguier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | /dls_bootstrap 2 | -------------------------------------------------------------------------------- /bootstrap/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "authors": [ 4 | "Laurent Tréguier" 5 | ], 6 | "description": "DLS bootstrap utility", 7 | "copyright": "Copyright © 2018, Laurent Tréguier", 8 | "license": "GPL-3.0 or later", 9 | "dependencies": { 10 | "dls:i18n": "*" 11 | }, 12 | "libs-windows": [ 13 | "wininet" 14 | ] 15 | } -------------------------------------------------------------------------------- /bootstrap/source/dls/util/getopt.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.getopt; 22 | 23 | import std.getopt : Option; 24 | 25 | void printHelp(const char[] title, const Option[] options, void delegate(const char[]) sink) 26 | { 27 | import std.ascii : newline; 28 | import std.algorithm : map, maxElement; 29 | 30 | sink(title); 31 | sink(newline); 32 | 33 | immutable longest = maxElement(options.map!(o => o.optLong.length + (o.optShort.length > 0 34 | ? o.optShort.length + 1 : 0))); 35 | 36 | foreach (option; options) 37 | { 38 | auto lineSize = option.optLong.length; 39 | sink(option.optLong); 40 | 41 | if (option.optShort.length > 0) 42 | { 43 | lineSize += option.optShort.length + 1; 44 | sink("|"); 45 | sink(option.optShort); 46 | } 47 | 48 | if (option.help.length > 0) 49 | { 50 | auto spaces = new char[longest + 1 - lineSize]; 51 | spaces[] = ' '; 52 | sink(spaces); 53 | sink(option.help); 54 | } 55 | 56 | sink(newline); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bootstrap/source/dls/util/setup.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.setup; 22 | 23 | void initialSetup() 24 | { 25 | version (Windows) 26 | { 27 | import std.algorithm : splitter; 28 | import std.file : exists; 29 | import std.path : buildNormalizedPath, dirName; 30 | import std.process : environment; 31 | 32 | version (X86_64) 33 | { 34 | enum binDir = "bin64"; 35 | } 36 | else 37 | { 38 | enum binDir = "bin"; 39 | } 40 | 41 | auto pathParts = splitter(environment["PATH"], ';'); 42 | 43 | foreach (path; pathParts) 44 | { 45 | if (exists(buildNormalizedPath(path, "dmd.exe"))) 46 | { 47 | environment["PATH"] = buildNormalizedPath(dirName(path), binDir) 48 | ~ ';' ~ environment["PATH"]; 49 | return; 50 | } 51 | } 52 | 53 | foreach (path; pathParts) 54 | { 55 | if (exists(buildNormalizedPath(path, "ldc2.exe"))) 56 | { 57 | environment["PATH"] = path ~ ';' ~ environment["PATH"]; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bootstrap/source/main.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | shared static this() 22 | { 23 | import dls.util.setup : initialSetup; 24 | 25 | initialSetup(); 26 | } 27 | 28 | int main(string[] args) 29 | { 30 | import dls.bootstrap : canDownloadDls, downloadDls, linkDls; 31 | import dls.util.i18n : Tr, tr; 32 | import dls.util.getopt : printHelp; 33 | import std.ascii : newline; 34 | import std.conv : text; 35 | import std.file : thisExePath; 36 | import std.format : format; 37 | import std.getopt : config, getopt; 38 | import std.path : dirName; 39 | import std.stdio : stderr, stdout; 40 | 41 | bool check; 42 | bool localization; 43 | bool progress; 44 | 45 | try 46 | { 47 | //dfmt off 48 | auto info = getopt(args, config.passThrough, 49 | "check|c", 50 | tr(Tr.bootstrap_help_check), 51 | &check, 52 | "localization|l", 53 | tr(Tr.bootstrap_help_localization), 54 | &localization, 55 | "progress|p", 56 | format(tr(Tr.bootstrap_help_progress)), 57 | &progress); 58 | //dfmt on 59 | 60 | if (info.helpWanted) 61 | { 62 | printHelp(tr(Tr.bootstrap_help_title), info.options, data => stdout.rawWrite(data)); 63 | return 0; 64 | } 65 | } 66 | catch (Exception e) 67 | { 68 | stderr.writeln(e.msg); 69 | return 1; 70 | } 71 | 72 | string output; 73 | int status; 74 | 75 | if (check) 76 | { 77 | immutable ok = canDownloadDls; 78 | output = text(ok); 79 | status = ok ? 0 : 1; 80 | } 81 | else 82 | { 83 | if (localization) 84 | { 85 | stderr.rawWrite("installing:" ~ tr(Tr.bootstrap_installDls_installing) ~ '\t'); 86 | stderr.rawWrite("downloading:" ~ tr(Tr.bootstrap_installDls_downloading) ~ '\t'); 87 | stderr.rawWrite("extracting:" ~ tr(Tr.bootstrap_installDls_extracting)); 88 | stderr.rawWrite(newline); 89 | stderr.flush(); 90 | } 91 | 92 | immutable printSize = progress ? (size_t size) { 93 | stderr.rawWrite(text(size)); 94 | stderr.rawWrite(newline); 95 | stderr.flush(); 96 | } : null; 97 | immutable printExtract = progress ? () { 98 | stderr.rawWrite("extract"); 99 | stderr.rawWrite(newline); 100 | stderr.flush(); 101 | } : null; 102 | 103 | downloadDls(printSize, printSize, printExtract); 104 | output = linkDls(); 105 | } 106 | 107 | stdout.rawWrite(output); 108 | stdout.rawWrite(newline); 109 | return status; 110 | } 111 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /*.txt -------------------------------------------------------------------------------- /data/data.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | void main() 22 | { 23 | import std.file : exists, readText, write; 24 | import std.path : buildNormalizedPath; 25 | import std.process : environment; 26 | 27 | immutable dataPath = buildNormalizedPath(environment["DUB_PACKAGE_DIR"], "data"); 28 | immutable fileFillers = [ 29 | "dls-version.txt" : environment.get("DUB_PACKAGE_VERSION", getVersionFromDescription()), 30 | "build-platform.txt" : environment.get("DUB_PLATFORM", "unknown-build-platform"), 31 | "build-arch.txt" : environment.get("DUB_ARCH", "unknown-build-arch"), 32 | "build-type.txt" : environment.get("DUB_BUILD_TYPE", "unknown-build-type"), 33 | "compiler-version.txt" : getCompilerVersion() 34 | ]; 35 | 36 | foreach (file, newContent; fileFillers) 37 | { 38 | immutable dataFile = buildNormalizedPath(dataPath, file); 39 | immutable oldContent = exists(dataFile) ? readText(dataFile) : ""; 40 | 41 | if (newContent != oldContent) 42 | { 43 | write(dataFile, newContent); 44 | } 45 | } 46 | } 47 | 48 | string getCompilerVersion() 49 | { 50 | import std.process : environment, execute; 51 | import std.regex : matchFirst, regex; 52 | 53 | return execute([environment["DC"], "--version"]).output.matchFirst(regex(`\d+\.\d+\.\d+`)).front; 54 | } 55 | 56 | string getVersionFromDescription() 57 | { 58 | import std.algorithm : find; 59 | import std.json : parseJSON; 60 | import std.process : Config, environment, execute; 61 | 62 | immutable describe = execute(["dub", "describe"], null, Config.none, size_t.max, environment["DUB_PACKAGE_DIR"]); 63 | immutable desc = parseJSON(describe.output); 64 | return desc["packages"].array.find!(p => p["name"] == desc["rootPackage"])[0]["version"].str; 65 | } 66 | -------------------------------------------------------------------------------- /dscanner.ini: -------------------------------------------------------------------------------- 1 | [analysis.config.StaticAnalysisConfig] 2 | undocumented_declaration_check="disabled" -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dls", 3 | "authors": [ 4 | "Laurent Tréguier" 5 | ], 6 | "description": "D Language Server", 7 | "copyright": "Copyright © 2018, Laurent Tréguier", 8 | "license": "GPL-3.0 or later", 9 | "stringImportPaths": [ 10 | "data" 11 | ], 12 | "dependencies": { 13 | "dls:bootstrap": "*", 14 | "dls:i18n": "*", 15 | "dls:protocol": "*", 16 | "dls:util": "*", 17 | "dub": "~>1.20.0", 18 | "dcd": "~>0.12.0", 19 | "dscanner": "~>0.8.0", 20 | "dfmt": "~>0.11.0" 21 | }, 22 | "versions": [ 23 | "StdLoggerDisableFatal" 24 | ], 25 | "preGenerateCommands": [ 26 | "dmd $PACKAGE_DIR/data/data.d -of=$PACKAGE_DIR/data/data.exe || ldc2 $PACKAGE_DIR/data/data.d -of=$PACKAGE_DIR/data/data.exe || gdc $PACKAGE_DIR/data/data.d -o $PACKAGE_DIR/data/data.exe", 27 | "$PACKAGE_DIR/data/data.exe" 28 | ], 29 | "subPackages": [ 30 | "bootstrap", 31 | "i18n", 32 | "protocol", 33 | "util" 34 | ] 35 | } -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "dcd": "0.12.0", 5 | "dfmt": "0.11.0", 6 | "dscanner": "0.8.0", 7 | "dsymbol": "0.9.2", 8 | "dub": "1.20.0", 9 | "emsi_containers": "0.8.0-alpha.19", 10 | "inifiled": "1.3.1", 11 | "libddoc": "0.7.1", 12 | "libdparse": "0.13.2", 13 | "msgpack-d": "1.0.2", 14 | "stdx-allocator": "2.77.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /i18n/data/translations.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | private immutable dubPackageDir = "DUB_PACKAGE_DIR"; 22 | 23 | void main() 24 | { 25 | import std.algorithm : sort; 26 | import std.ascii : newline; 27 | import std.file : readText, write; 28 | import std.format : format; 29 | import std.json : parseJSON; 30 | import std.path : asRelativePath, buildNormalizedPath; 31 | import std.process : environment; 32 | import std.range : replace; 33 | 34 | immutable packageDir = environment[dubPackageDir]; 35 | immutable translationsPath = buildNormalizedPath(packageDir, "data", "translations.json"); 36 | immutable trModulePath = buildNormalizedPath(packageDir, "source", "dls", 37 | "util", "i18n", "constants.d"); 38 | immutable trModuleContent = readText(trModulePath); 39 | auto translations = parseJSON(readText(translationsPath)); 40 | string content; 41 | 42 | content ~= format(q{/+ This file is generated automatically by %s.d +/}, __MODULE__); 43 | content ~= newline ~ newline; 44 | content ~= q{module dls.util.i18n.constants;}; 45 | content ~= newline ~ newline; 46 | content ~= q{enum Tr : string}; 47 | content ~= newline; 48 | content ~= "{"; 49 | content ~= newline; 50 | content ~= q{ _ = "### BAD TRANSLATION KEY ###",}; 51 | content ~= newline; 52 | 53 | foreach (key; sort(translations.object.keys)) 54 | { 55 | content ~= format(" %s = \"%s\"%s" ~ newline, key.replace(".", "_"), key, ","); 56 | } 57 | 58 | content ~= "}"; 59 | content ~= newline; 60 | 61 | if (content != trModuleContent) 62 | { 63 | write(trModulePath, content); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /i18n/data/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "app.help.title": { 3 | "en": "D Language Server", 4 | "fr": "Serveur de Language D" 5 | }, 6 | "app.help.version": { 7 | "en": "Display program version", 8 | "fr": "Afficher la version du programme" 9 | }, 10 | "app.help.stdio": { 11 | "en": "Use standard input and output streams", 12 | "fr": "Utiliser l'entrée et la sortie standards" 13 | }, 14 | "app.help.socket": { 15 | "en": "Value: Connect to a socket on a specified port", 16 | "fr": "Valeur: Se connecter à une socket sur le port spécifié" 17 | }, 18 | "app.version.dlsVersion": { 19 | "en": "DLS version $1 [$2, $3, $4, $5]", 20 | "fr": "DLS version $1 [$2, $3, $4, $5]" 21 | }, 22 | "app.version.compilerVersion": { 23 | "en": "Compiled with $1 $2 (DMDFE: $3)", 24 | "fr": "Compilé avec $1 $2 (DMDFE: $3)" 25 | }, 26 | "app.rpc.errorCodes.parseError": { 27 | "en": "Parse error", 28 | "fr": "Erreur d'analyse" 29 | }, 30 | "app.rpc.errorCodes.invalidRequest": { 31 | "en": "Invalid request", 32 | "fr": "Requête invalide" 33 | }, 34 | "app.rpc.errorCodes.methodNotFound": { 35 | "en": "Method not found", 36 | "fr": "Méthode non trouvée" 37 | }, 38 | "app.rpc.errorCodes.invalidParams": { 39 | "en": "Invalid parameters", 40 | "fr": "paramètres invalides" 41 | }, 42 | "app.rpc.errorCodes.internalError": { 43 | "en": "Internal error", 44 | "fr": "Erreur interne" 45 | }, 46 | "app.rpc.errorCodes.serverNotInitialized": { 47 | "en": "Server not initialized", 48 | "fr": "Serveur non initialisé" 49 | }, 50 | "app.rpc.errorCodes.unknownErrorCode": { 51 | "en": "Unknown error code", 52 | "fr": "Code d'erreur inconnu" 53 | }, 54 | "app.rpc.errorCodes.requestCancelled": { 55 | "en": "Request cancelled", 56 | "fr": "Requête annulée" 57 | }, 58 | "app.upgradeSelections": { 59 | "en": "Dependencies for project $1 changed", 60 | "fr": "Dépendences du projet $1 modifiées" 61 | }, 62 | "app.upgradeSelections.upgrade": { 63 | "en": "Upgrade selections", 64 | "fr": "Mettre à jour les sélections" 65 | }, 66 | "app.upgradeSelections.upgrading": { 67 | "en": "Upgrading selections", 68 | "fr": "Mise à jour des sélections" 69 | }, 70 | "app.upgradeSelections.error": { 71 | "_type": 1, 72 | "en": "$1", 73 | "fr": "$1" 74 | }, 75 | "app.upgradeDls": { 76 | "en": "DLS version $1 is available (current version: $2)", 77 | "fr": "DLS version $1 est disponible (version actuelle: $2)" 78 | }, 79 | "app.upgradeDls.upgrade": { 80 | "en": "Upgrade", 81 | "fr": "Mettre à jour" 82 | }, 83 | "app.upgradeDls.upgrading": { 84 | "en": "Upgrading DLS", 85 | "fr": "Mise à jour de DLS" 86 | }, 87 | "app.upgradeDls.downloading": { 88 | "en": "Downloading", 89 | "fr": "Téléchargement" 90 | }, 91 | "app.upgradeDls.extracting": { 92 | "en": "Extracting", 93 | "fr": "Extraction" 94 | }, 95 | "app.upgradeDls.downloadError": { 96 | "en": "DLS could not be downloaded", 97 | "fr": "DLS n'a pas pu être téléchargé" 98 | }, 99 | "app.upgradeDls.linkError": { 100 | "_type": 1, 101 | "en": "The symlink to DLS could not br created", 102 | "fr": "Le lien symbolique vers DLS n'a pas pu être créé" 103 | }, 104 | "app.showChangelog": { 105 | "en": "DLS upgraded to version $1", 106 | "fr": "DLS mis à jour vers la version $1" 107 | }, 108 | "app.showChangelog.show": { 109 | "en": "See what's new", 110 | "fr": "Voir les nouveautés" 111 | }, 112 | "app.command.diagnostic.disableCheck.local": { 113 | "en": "Disable $1 (this line)", 114 | "fr": "Désactiver $1 (cette ligne)" 115 | }, 116 | "app.command.diagnostic.disableCheck.global": { 117 | "en": "Disable $1 (project-wide)", 118 | "fr": "Désactiver $1 (tout le projet)" 119 | }, 120 | "bootstrap.help.title": { 121 | "en": "DLS installer", 122 | "fr": "Installateur de DLS" 123 | }, 124 | "bootstrap.help.default": { 125 | "en": "default", 126 | "fr": "défaut" 127 | }, 128 | "bootstrap.help.check": { 129 | "en": "Checks if the selected method is available.", 130 | "fr": "Vérifie si la méthode sélectionnée est disponible." 131 | }, 132 | "bootstrap.help.localization": { 133 | "en": "Output localization strings.", 134 | "fr": "Émettre des chaînes pour la localisation." 135 | }, 136 | "bootstrap.help.progress": { 137 | "en": "Show progress.", 138 | "fr": "Montrer le progrès." 139 | }, 140 | "bootstrap.installDls.installing": { 141 | "en": "Installing DLS", 142 | "fr": "Installation de DLS" 143 | }, 144 | "bootstrap.installDls.downloading": { 145 | "en": "Downloading", 146 | "fr": "Téléchargement" 147 | }, 148 | "bootstrap.installDls.extracting": { 149 | "en": "Extracting", 150 | "fr": "Extraction" 151 | } 152 | } -------------------------------------------------------------------------------- /i18n/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18n", 3 | "authors": [ 4 | "Laurent Tréguier" 5 | ], 6 | "description": "DLS translation utilities", 7 | "copyright": "Copyright © 2018, Laurent Tréguier", 8 | "license": "GPL-3.0 or later", 9 | "stringImportPaths": [ 10 | "data" 11 | ], 12 | "dependencies": { 13 | "dls:protocol": "*" 14 | }, 15 | "preGenerateCommands": [ 16 | "dmd $PACKAGE_DIR/data/translations.d -of=$PACKAGE_DIR/data/translations.exe || ldc2 $PACKAGE_DIR/data/translations.d -of=$PACKAGE_DIR/data/translations.exe || gdc $PACKAGE_DIR/data/translations.d -o $PACKAGE_DIR/data/translations.exe", 17 | "$PACKAGE_DIR/data/translations.exe" 18 | ] 19 | } -------------------------------------------------------------------------------- /i18n/source/dls/util/i18n/constants.d: -------------------------------------------------------------------------------- 1 | /+ This file is generated automatically by translations.d +/ 2 | 3 | module dls.util.i18n.constants; 4 | 5 | enum Tr : string 6 | { 7 | _ = "### BAD TRANSLATION KEY ###", 8 | app_command_diagnostic_disableCheck_global = "app.command.diagnostic.disableCheck.global", 9 | app_command_diagnostic_disableCheck_local = "app.command.diagnostic.disableCheck.local", 10 | app_help_socket = "app.help.socket", 11 | app_help_stdio = "app.help.stdio", 12 | app_help_title = "app.help.title", 13 | app_help_version = "app.help.version", 14 | app_rpc_errorCodes_internalError = "app.rpc.errorCodes.internalError", 15 | app_rpc_errorCodes_invalidParams = "app.rpc.errorCodes.invalidParams", 16 | app_rpc_errorCodes_invalidRequest = "app.rpc.errorCodes.invalidRequest", 17 | app_rpc_errorCodes_methodNotFound = "app.rpc.errorCodes.methodNotFound", 18 | app_rpc_errorCodes_parseError = "app.rpc.errorCodes.parseError", 19 | app_rpc_errorCodes_requestCancelled = "app.rpc.errorCodes.requestCancelled", 20 | app_rpc_errorCodes_serverNotInitialized = "app.rpc.errorCodes.serverNotInitialized", 21 | app_rpc_errorCodes_unknownErrorCode = "app.rpc.errorCodes.unknownErrorCode", 22 | app_showChangelog = "app.showChangelog", 23 | app_showChangelog_show = "app.showChangelog.show", 24 | app_upgradeDls = "app.upgradeDls", 25 | app_upgradeDls_downloadError = "app.upgradeDls.downloadError", 26 | app_upgradeDls_downloading = "app.upgradeDls.downloading", 27 | app_upgradeDls_extracting = "app.upgradeDls.extracting", 28 | app_upgradeDls_linkError = "app.upgradeDls.linkError", 29 | app_upgradeDls_upgrade = "app.upgradeDls.upgrade", 30 | app_upgradeDls_upgrading = "app.upgradeDls.upgrading", 31 | app_upgradeSelections = "app.upgradeSelections", 32 | app_upgradeSelections_error = "app.upgradeSelections.error", 33 | app_upgradeSelections_upgrade = "app.upgradeSelections.upgrade", 34 | app_upgradeSelections_upgrading = "app.upgradeSelections.upgrading", 35 | app_version_compilerVersion = "app.version.compilerVersion", 36 | app_version_dlsVersion = "app.version.dlsVersion", 37 | bootstrap_help_check = "bootstrap.help.check", 38 | bootstrap_help_default = "bootstrap.help.default", 39 | bootstrap_help_localization = "bootstrap.help.localization", 40 | bootstrap_help_progress = "bootstrap.help.progress", 41 | bootstrap_help_title = "bootstrap.help.title", 42 | bootstrap_installDls_downloading = "bootstrap.installDls.downloading", 43 | bootstrap_installDls_extracting = "bootstrap.installDls.extracting", 44 | bootstrap_installDls_installing = "bootstrap.installDls.installing", 45 | } 46 | -------------------------------------------------------------------------------- /i18n/source/dls/util/i18n/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.i18n; 22 | 23 | public import dls.util.i18n.constants; 24 | import dls.protocol.interfaces : MessageType; 25 | import std.json : JSONValue; 26 | 27 | private enum translationsJson = import("translations.json"); 28 | private immutable JSONValue translations; 29 | private immutable string locale; 30 | private immutable defaultLocale = "en"; 31 | 32 | shared static this() 33 | { 34 | import std.json : parseJSON; 35 | 36 | translations = parseJSON(translationsJson); 37 | locale = defaultLocale; 38 | 39 | version (Windows) 40 | { 41 | import core.sys.windows.windef : DWORD, ERROR_SUCCESS, LONG, HKEY; 42 | import core.sys.windows.winnt : KEY_READ; 43 | import core.sys.windows.winreg : HKEY_USERS, RegOpenKeyExA, RegQueryValueExA; 44 | import std.string : toStringz; 45 | 46 | HKEY hKey; 47 | auto regKeyCStr = toStringz(`.DEFAULT\Control Panel\International`); 48 | 49 | if (RegOpenKeyExA(HKEY_USERS, regKeyCStr, 0, KEY_READ, &hKey) != ERROR_SUCCESS) 50 | { 51 | return; 52 | } 53 | 54 | DWORD size = 32; 55 | auto buffer = new char[size]; 56 | auto localeNameCStr = toStringz("LocaleName"); 57 | 58 | if (RegQueryValueExA(hKey, localeNameCStr, null, null, buffer.ptr, &size) != ERROR_SUCCESS) 59 | { 60 | return; 61 | } 62 | 63 | if (size >= 2) 64 | { 65 | locale = buffer[0 .. 2].idup; 66 | } 67 | } 68 | else version (Posix) 69 | { 70 | import std.process : environment; 71 | 72 | auto lang = environment.get("LANG", defaultLocale); 73 | 74 | if (lang.length >= 2) 75 | { 76 | locale = lang[0 .. 2]; 77 | } 78 | } 79 | else 80 | { 81 | locale = defaultLocale; 82 | } 83 | } 84 | 85 | string tr(Tr identifier, string[] args = []) 86 | { 87 | import std.ascii : newline; 88 | import std.conv : text; 89 | import std.range : replace; 90 | 91 | auto message = translations[identifier]; 92 | auto localizedMessage = message[locale in message ? locale : defaultLocale].str; 93 | 94 | foreach (i, arg; args) 95 | { 96 | localizedMessage = localizedMessage.replace('$' ~ text(i + 1), arg); 97 | } 98 | 99 | return localizedMessage.replace("$n", newline); 100 | } 101 | 102 | MessageType trType(Tr message) 103 | { 104 | auto t = translations[message]; 105 | return "_type" in t ? cast(MessageType) t["_type"].integer : MessageType.info; 106 | } 107 | -------------------------------------------------------------------------------- /protocol/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protocol", 3 | "authors": [ 4 | "Laurent Tréguier" 5 | ], 6 | "description": "LSP interfaces implementations", 7 | "copyright": "Copyright © 2018, Laurent Tréguier", 8 | "license": "GPL-3.0 or later" 9 | } -------------------------------------------------------------------------------- /protocol/source/dls/protocol/errors.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.errors; 22 | 23 | class InvalidParamsException : Exception 24 | { 25 | this(string msg) 26 | { 27 | super("Invalid parameters: " ~ msg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /protocol/source/dls/protocol/interfaces/client.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces.client; 22 | 23 | private abstract class RegistrationBase 24 | { 25 | string id; 26 | string method; 27 | 28 | @safe this(string id, string method) pure nothrow 29 | { 30 | this.id = id; 31 | this.method = method; 32 | } 33 | } 34 | 35 | package interface RegistrationOptions 36 | { 37 | } 38 | 39 | final class Registration(R : RegistrationOptions) : RegistrationBase 40 | { 41 | import std.typecons : Nullable; 42 | 43 | Nullable!R registerOptions; 44 | 45 | @safe this(string id = string.init, string method = string.init, 46 | Nullable!R registerOptions = Nullable!R.init) pure nothrow 47 | { 48 | super(id, method); 49 | this.registerOptions = registerOptions; 50 | } 51 | } 52 | 53 | class TextDocumentRegistrationOptions : RegistrationOptions 54 | { 55 | import dls.protocol.definitions : DocumentSelector; 56 | import std.typecons : Nullable; 57 | 58 | Nullable!DocumentSelector documentSelector; 59 | 60 | @safe this(Nullable!DocumentSelector documentSelector = Nullable!DocumentSelector.init) pure nothrow 61 | { 62 | this.documentSelector = documentSelector; 63 | } 64 | } 65 | 66 | final class RegistrationParams(R : RegistrationOptions) 67 | { 68 | Registration!R[] registrations; 69 | 70 | @safe this(Registration!R[] registrations = Registration!R[].init) pure nothrow 71 | { 72 | this.registrations = registrations; 73 | } 74 | } 75 | 76 | final class Unregistration : RegistrationBase 77 | { 78 | @safe this(string id = string.init, string method = string.init) pure nothrow 79 | { 80 | super(id, method); 81 | } 82 | } 83 | 84 | final class UnregistrationParams 85 | { 86 | Unregistration[] unregistrations; 87 | 88 | @safe this(Unregistration[] unregistrations = Unregistration[].init) pure nothrow 89 | { 90 | this.unregistrations = unregistrations; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /protocol/source/dls/protocol/interfaces/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces; 22 | 23 | public import dls.protocol.interfaces.client; 24 | public import dls.protocol.interfaces.general; 25 | public import dls.protocol.interfaces.text_document; 26 | public import dls.protocol.interfaces.window; 27 | public import dls.protocol.interfaces.workspace; 28 | -------------------------------------------------------------------------------- /protocol/source/dls/protocol/interfaces/window.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces.window; 22 | 23 | class ShowMessageParams 24 | { 25 | MessageType type; 26 | string message; 27 | 28 | @safe this(MessageType type = MessageType.init, string message = string.init) pure nothrow 29 | { 30 | this.type = type; 31 | this.message = message; 32 | } 33 | } 34 | 35 | enum MessageType : ubyte 36 | { 37 | error = 1, 38 | warning = 2, 39 | info = 3, 40 | log = 4 41 | } 42 | 43 | final class ShowMessageRequestParams : ShowMessageParams 44 | { 45 | import std.typecons : Nullable; 46 | 47 | Nullable!(MessageActionItem[]) actions; 48 | 49 | @safe this(MessageType type = MessageType.init, string message = string.init, 50 | Nullable!(MessageActionItem[]) actions = Nullable!(MessageActionItem[]).init) pure nothrow 51 | { 52 | super(type, message); 53 | this.actions = actions; 54 | } 55 | } 56 | 57 | final class MessageActionItem 58 | { 59 | string title; 60 | 61 | @safe this(string title = string.init) pure nothrow 62 | { 63 | this.title = title; 64 | } 65 | } 66 | 67 | alias LogMessageParams = ShowMessageParams; 68 | -------------------------------------------------------------------------------- /protocol/source/dls/protocol/interfaces/workspace.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces.workspace; 22 | 23 | import dls.protocol.interfaces.client : RegistrationOptions; 24 | 25 | final class WorkspaceFolder 26 | { 27 | import dls.protocol.definitions : DocumentUri; 28 | 29 | DocumentUri uri; 30 | string name; 31 | 32 | @safe this() pure nothrow 33 | { 34 | } 35 | } 36 | 37 | final class DidChangeWorkspaceFoldersParams 38 | { 39 | WorkspaceFoldersChangeEvent event; 40 | 41 | @safe this() pure nothrow 42 | { 43 | } 44 | } 45 | 46 | final class WorkspaceFoldersChangeEvent 47 | { 48 | WorkspaceFolder[] added; 49 | WorkspaceFolder[] removed; 50 | 51 | @safe this() pure nothrow 52 | { 53 | } 54 | } 55 | 56 | final class DidChangeConfigurationParams 57 | { 58 | import std.json : JSONValue; 59 | 60 | JSONValue settings; 61 | 62 | @safe this() pure nothrow 63 | { 64 | } 65 | } 66 | 67 | final class ConfigurationParams 68 | { 69 | ConfigurationItem[] items; 70 | 71 | @safe this(ConfigurationItem[] items = ConfigurationItem[].init) pure nothrow 72 | { 73 | this.items = items; 74 | } 75 | } 76 | 77 | final class ConfigurationItem 78 | { 79 | import dls.protocol.definitions : DocumentUri; 80 | import std.typecons : Nullable; 81 | 82 | Nullable!DocumentUri scopeUri; 83 | Nullable!string section; 84 | 85 | @safe this(Nullable!DocumentUri scopeUri = Nullable!DocumentUri.init, 86 | Nullable!string section = Nullable!string.init) pure nothrow 87 | { 88 | this.scopeUri = scopeUri; 89 | this.section = section; 90 | } 91 | } 92 | 93 | final class DidChangeWatchedFilesParams 94 | { 95 | FileEvent[] changes; 96 | 97 | @safe this() pure nothrow 98 | { 99 | } 100 | } 101 | 102 | final class FileEvent 103 | { 104 | import dls.protocol.definitions : DocumentUri; 105 | 106 | DocumentUri uri; 107 | FileChangeType type; 108 | 109 | @safe this() pure nothrow 110 | { 111 | } 112 | } 113 | 114 | enum FileChangeType : ubyte 115 | { 116 | created = 1, 117 | changed = 2, 118 | deleted = 3 119 | } 120 | 121 | final class DidChangeWatchedFilesRegistrationOptions : RegistrationOptions 122 | { 123 | FileSystemWatcher[] watchers; 124 | 125 | @safe this(FileSystemWatcher[] watchers = FileSystemWatcher[].init) pure nothrow 126 | { 127 | this.watchers = watchers; 128 | } 129 | } 130 | 131 | final class FileSystemWatcher 132 | { 133 | import std.typecons : Nullable; 134 | 135 | string globPattern; 136 | Nullable!ubyte kind; 137 | 138 | @safe this(string globPattern = string.init, Nullable!ubyte kind = Nullable!ubyte.init) pure nothrow 139 | { 140 | this.globPattern = globPattern; 141 | this.kind = kind; 142 | } 143 | } 144 | 145 | enum WatchKind : ubyte 146 | { 147 | create = 1, 148 | change = 2, 149 | delete_ = 4 150 | } 151 | 152 | final class WorkspaceSymbolParams 153 | { 154 | string query; 155 | 156 | @safe this() pure nothrow 157 | { 158 | } 159 | } 160 | 161 | final class ExecuteCommandParams 162 | { 163 | import std.json : JSONValue; 164 | import std.typecons : Nullable; 165 | 166 | string command; 167 | Nullable!(JSONValue[]) arguments; 168 | 169 | @safe this() pure nothrow 170 | { 171 | } 172 | } 173 | 174 | final class ExecuteCommandRegistrationOptions : RegistrationOptions 175 | { 176 | string[] commands; 177 | 178 | @safe this(string[] commands = string[].init) pure nothrow 179 | { 180 | this.commands = commands; 181 | } 182 | } 183 | 184 | final class ApplyWorkspaceEditParams 185 | { 186 | import dls.protocol.definitions : WorkspaceEdit; 187 | 188 | WorkspaceEdit edit; 189 | 190 | @safe this(WorkspaceEdit edit = WorkspaceEdit.init) pure nothrow 191 | { 192 | this.edit = edit; 193 | } 194 | } 195 | 196 | final class ApplyWorkspaceEditResponse 197 | { 198 | bool applied; 199 | 200 | @safe this() pure nothrow 201 | { 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /source/dls/info.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.info; 22 | 23 | immutable currentVersion = import("dls-version.txt"); 24 | immutable buildPlatform = import("build-platform.txt"); 25 | immutable buildArch = import("build-arch.txt"); 26 | immutable buildType = import("build-type.txt"); 27 | immutable compilerVersion = import("compiler-version.txt"); 28 | -------------------------------------------------------------------------------- /source/dls/protocol/handlers.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.handlers; 22 | 23 | import std.json : JSONValue; 24 | import std.traits : isSomeFunction; 25 | import std.typecons : Nullable; 26 | 27 | alias RequestHandler = Nullable!JSONValue delegate(Nullable!JSONValue); 28 | alias NotificationHandler = void delegate(Nullable!JSONValue); 29 | alias ResponseHandler = void delegate(string id, Nullable!JSONValue); 30 | 31 | private shared RequestHandler[string] requestHandlers; 32 | private shared NotificationHandler[string] notificationHandlers; 33 | private shared ResponseHandler[string] responseHandlers; 34 | private shared ResponseHandler[string] runtimeResponseHandlers; 35 | 36 | class HandlerNotFoundException : Exception 37 | { 38 | this(const string method) 39 | { 40 | super("No handler found for method " ~ method); 41 | } 42 | } 43 | 44 | /++ 45 | Checks if a method has a response handler registered for it. Used to determine 46 | if the server should send a request or a notification to the client (if the 47 | method has a response handler, then the server will expect a response and thus 48 | send a request instead of a notification). 49 | +/ 50 | bool hasResponseHandler(const string method) 51 | { 52 | return (method in responseHandlers) !is null; 53 | } 54 | 55 | /++ 56 | Registers a new handler of any kind (`RequestHandler`, `NotificationHandler` or 57 | `ResponseHandler`). 58 | +/ 59 | void pushHandler(F)(const string method, F func) 60 | if (isSomeFunction!F && !is(F == RequestHandler) 61 | && !is(F == NotificationHandler) && !is(F == ResponseHandler)) 62 | { 63 | import dls.util.json : convertFromJSON; 64 | import std.traits : Parameters, ReturnType; 65 | 66 | static if ((Parameters!F).length == 1) 67 | { 68 | pushHandler(method, (Nullable!JSONValue params) { 69 | import dls.util.json : convertToJSON; 70 | 71 | auto arg = convertFromJSON!((Parameters!F)[0])(params.isNull ? JSONValue(null) : params); 72 | 73 | static if (is(ReturnType!F == void)) 74 | { 75 | func(arg); 76 | } 77 | else 78 | { 79 | return convertToJSON(func(arg)); 80 | } 81 | }); 82 | } 83 | else static if ((Parameters!F).length == 2) 84 | { 85 | pushHandler(method, (string id, Nullable!JSONValue params) => func(id, 86 | convertFromJSON!((Parameters!F)[1])(params.isNull ? JSONValue(null) : params))); 87 | } 88 | else 89 | { 90 | static assert(false); 91 | } 92 | } 93 | 94 | /++ Registers a new static `RequestHandler`. +/ 95 | private void pushHandler(const string method, RequestHandler h) 96 | { 97 | requestHandlers[method] = h; 98 | } 99 | 100 | /++ Registers a new static `NotificationHandler`. +/ 101 | private void pushHandler(const string method, NotificationHandler h) 102 | { 103 | notificationHandlers[method] = h; 104 | } 105 | 106 | /++ Registers a new static `ResponseHandler`. +/ 107 | private void pushHandler(const string method, ResponseHandler h) 108 | { 109 | responseHandlers[method] = h; 110 | } 111 | 112 | /++ Registers a new dynamic `ResponseHandler` (used at runtime) +/ 113 | void pushHandler(const string id, const string method) 114 | { 115 | runtimeResponseHandlers[id] = responseHandlers[method]; 116 | } 117 | 118 | /++ 119 | Returns the `RequestHandler`/`NotificationHandler`/`ResponseHandler` 120 | corresponding to a specific LSP method. 121 | +/ 122 | T handler(T)(const string methodOrId) 123 | if (is(T == RequestHandler) || is(T == NotificationHandler) || is(T == ResponseHandler)) 124 | { 125 | static if (is(T == RequestHandler)) 126 | { 127 | alias handlers = requestHandlers; 128 | } 129 | else static if (is(T == NotificationHandler)) 130 | { 131 | alias handlers = notificationHandlers; 132 | } 133 | else 134 | { 135 | alias handlers = runtimeResponseHandlers; 136 | } 137 | 138 | if (methodOrId in handlers) 139 | { 140 | auto h = handlers[methodOrId]; 141 | 142 | static if (is(T == ResponseHandler)) 143 | { 144 | runtimeResponseHandlers.remove(methodOrId); 145 | } 146 | 147 | return h; 148 | } 149 | 150 | throw new HandlerNotFoundException(methodOrId); 151 | } 152 | -------------------------------------------------------------------------------- /source/dls/protocol/interfaces/dls.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces.dls; 22 | 23 | class TranslationParams 24 | { 25 | import dls.util.i18n : Tr; 26 | 27 | string tr; 28 | 29 | this(Tr tr = Tr._, string[] args = string[].init) 30 | { 31 | static import dls.util.i18n; 32 | 33 | this.tr = dls.util.i18n.tr(tr, args); 34 | } 35 | } 36 | 37 | class DlsUpgradeSizeParams : TranslationParams 38 | { 39 | import dls.util.i18n : Tr; 40 | 41 | size_t size; 42 | 43 | this(Tr tr = Tr._, string[] args = string[].init, size_t size = size_t.init) 44 | { 45 | super(tr, args); 46 | this.size = size; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/dls/protocol/interfaces/other.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.interfaces.other; 22 | 23 | class SetTraceParams 24 | { 25 | import dls.protocol.interfaces : InitializeParams; 26 | 27 | InitializeParams.Trace value; 28 | } 29 | -------------------------------------------------------------------------------- /source/dls/protocol/jsonrpc.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.jsonrpc; 22 | 23 | import dls.util.i18n : Tr; 24 | import std.json : JSONValue; 25 | import std.typecons : Nullable, Tuple, tuple; 26 | 27 | private enum jsonrpcVersion = "2.0"; 28 | private enum jsonrpcSeparator = "\r\n\r\n"; 29 | 30 | abstract class Message 31 | { 32 | string jsonrpc = jsonrpcVersion; 33 | } 34 | 35 | class RequestMessage : Message 36 | { 37 | JSONValue id; 38 | string method; 39 | Nullable!JSONValue params; 40 | } 41 | 42 | class ResponseMessage : Message 43 | { 44 | JSONValue id; 45 | Nullable!JSONValue result; 46 | Nullable!ResponseError error; 47 | } 48 | 49 | class ResponseError 50 | { 51 | long code; 52 | string message; 53 | Nullable!JSONValue data; 54 | 55 | static ResponseError fromErrorCode(ErrorCodes errorCode, JSONValue data) 56 | { 57 | import dls.util.i18n : tr; 58 | import std.typecons : nullable; 59 | 60 | auto response = new ResponseError(); 61 | response.code = errorCode[0]; 62 | response.message = tr(errorCode[1]); 63 | response.data = data; 64 | return response.nullable; 65 | } 66 | } 67 | 68 | class NotificationMessage : Message 69 | { 70 | string method; 71 | Nullable!JSONValue params; 72 | } 73 | 74 | enum ErrorCodes : Tuple!(long, Tr) 75 | { 76 | //dfmt off 77 | parseError = tuple(-32_700L, Tr.app_rpc_errorCodes_parseError), 78 | invalidRequest = tuple(-32_600L, Tr.app_rpc_errorCodes_invalidRequest), 79 | methodNotFound = tuple(-32_601L, Tr.app_rpc_errorCodes_methodNotFound), 80 | invalidParams = tuple(-32_602L, Tr.app_rpc_errorCodes_invalidParams), 81 | internalError = tuple(-32_603L, Tr.app_rpc_errorCodes_internalError), 82 | serverErrorStart = tuple(-32_099L, Tr._), 83 | serverErrorEnd = tuple(-32_000L, Tr._), 84 | serverNotInitialized = tuple(-32_002L, Tr.app_rpc_errorCodes_serverNotInitialized), 85 | unknownErrorCode = tuple(-32_001L, Tr.app_rpc_errorCodes_unknownErrorCode), 86 | requestCancelled = tuple(-32_800L, Tr.app_rpc_errorCodes_requestCancelled), 87 | //dfmt on 88 | } 89 | 90 | class CancelParams 91 | { 92 | JSONValue id; 93 | } 94 | 95 | void sendError(ErrorCodes error, RequestMessage request, JSONValue data) 96 | { 97 | import std.typecons : nullable; 98 | 99 | if (request !is null) 100 | { 101 | send(request.id, Nullable!JSONValue(), ResponseError.fromErrorCode(error, data).nullable); 102 | } 103 | } 104 | 105 | /// Sends a request or a notification message. 106 | string send(string method, Nullable!JSONValue params = Nullable!JSONValue()) 107 | { 108 | import dls.protocol.handlers : hasResponseHandler, pushHandler; 109 | import dls.protocol.logger : logger; 110 | import std.uuid : randomUUID; 111 | 112 | if (hasResponseHandler(method)) 113 | { 114 | immutable id = randomUUID().toString(); 115 | pushHandler(id, method); 116 | logger.log(`Sending request "%s": %s`, id, method); 117 | send!RequestMessage(JSONValue(id), method, params, Nullable!ResponseError()); 118 | return id; 119 | } 120 | 121 | logger.log("Sending notification: %s", method); 122 | send!NotificationMessage(JSONValue(), method, params, Nullable!ResponseError()); 123 | return null; 124 | } 125 | 126 | /// Sends a request or a notification message. 127 | string send(T)(string method, T params) if (!is(T : Nullable!JSONValue)) 128 | { 129 | import dls.util.json : convertToJSON; 130 | import std.typecons : nullable; 131 | 132 | return send(method, convertToJSON(params)); 133 | } 134 | 135 | /// Sends a response message. 136 | void send(JSONValue id, Nullable!JSONValue result, 137 | Nullable!ResponseError error = Nullable!ResponseError()) 138 | { 139 | import dls.protocol.logger : logger; 140 | 141 | logger.log("Sending response with %s for request %s", error.isNull ? "result" : "error", id); 142 | send!ResponseMessage(id, null, result, error); 143 | } 144 | 145 | /// Sends a response message. 146 | private void send(T : Message)(JSONValue id, string method, 147 | Nullable!JSONValue payload, Nullable!ResponseError error) 148 | { 149 | import std.meta : AliasSeq; 150 | import std.traits : select; 151 | 152 | auto message = new T(); 153 | 154 | __traits(getMember, message, select!(__traits(hasMember, T, "params"))("params", "result")) = payload; 155 | 156 | foreach (member; AliasSeq!("id", "method", "error")) 157 | { 158 | static if (__traits(hasMember, T, member)) 159 | { 160 | mixin("message." ~ member ~ " = " ~ member ~ ";"); 161 | } 162 | } 163 | 164 | send(message); 165 | } 166 | 167 | private void send(T : Message)(T m) 168 | { 169 | import dls.util.communicator : communicator; 170 | import dls.util.json : convertToJSON; 171 | import std.conv : text; 172 | 173 | auto message = convertToJSON(m); 174 | auto messageString = message.get().toString(); 175 | 176 | synchronized 177 | { 178 | communicator.write( 179 | "Content-Length: " 180 | ~ text(messageString.length) 181 | ~ jsonrpcSeparator 182 | ~ messageString 183 | ); 184 | 185 | communicator.flush(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /source/dls/protocol/logger.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.logger; 22 | 23 | import dls.protocol.interfaces : InitializeParams, MessageType; 24 | 25 | private shared _logger = new shared LspLogger(); 26 | private immutable int[InitializeParams.Trace] traceToType; 27 | private immutable string[MessageType] messageSeverity; 28 | private immutable logMessageFormat = "[%.24s] [%s] %s"; 29 | 30 | shared static this() 31 | { 32 | import std.experimental.logger : LogLevel, globalLogLevel; 33 | 34 | globalLogLevel = LogLevel.off; 35 | 36 | //dfmt off 37 | traceToType = [ 38 | InitializeParams.Trace.off : 0, 39 | InitializeParams.Trace.messages : MessageType.info, 40 | InitializeParams.Trace.verbose : MessageType.log 41 | ]; 42 | 43 | messageSeverity = [ 44 | MessageType.log : "D", 45 | MessageType.info : "I", 46 | MessageType.warning : "W", 47 | MessageType.error : "E" 48 | ]; 49 | //dfmt on 50 | } 51 | 52 | @property shared(LspLogger) logger() 53 | { 54 | return _logger; 55 | } 56 | 57 | private shared class LspLogger 58 | { 59 | import dls.protocol.interfaces : MessageType; 60 | import std.format : format; 61 | 62 | private int _messageType; 63 | 64 | @property void trace(const InitializeParams.Trace t) 65 | { 66 | _messageType = traceToType[t]; 67 | } 68 | 69 | void log(Args...)(const string message, const Args args) const 70 | { 71 | sendMessage(format(message, args), MessageType.log); 72 | } 73 | 74 | void info(Args...)(const string message, const Args args) const 75 | { 76 | sendMessage(format(message, args), MessageType.info); 77 | } 78 | 79 | void warning(Args...)(const string message, const Args args) const 80 | { 81 | sendMessage(format(message, args), MessageType.warning); 82 | } 83 | 84 | void error(Args...)(const string message, const Args args) const 85 | { 86 | sendMessage(format(message, args), MessageType.error); 87 | } 88 | 89 | private void sendMessage(const string message, const MessageType type) const 90 | { 91 | import dls.protocol.interfaces : LogMessageParams; 92 | import dls.protocol.jsonrpc : send; 93 | import dls.protocol.messages.methods : Window; 94 | import dls.protocol.state : initOptions; 95 | import std.datetime : Clock; 96 | import std.file : mkdirRecurse; 97 | import std.format : format; 98 | import std.path : dirName; 99 | import std.stdio : File; 100 | 101 | if (type <= _messageType) 102 | { 103 | if (initOptions.logFile.length > 0) 104 | { 105 | static bool firstLog = true; 106 | 107 | if (firstLog) 108 | { 109 | mkdirRecurse(dirName(initOptions.logFile)); 110 | } 111 | 112 | synchronized 113 | { 114 | auto log = File(initOptions.logFile, firstLog ? "w" : "a"); 115 | log.writefln(logMessageFormat, Clock.currTime.toString(), 116 | messageSeverity[type], message); 117 | log.flush(); 118 | } 119 | 120 | if (firstLog) 121 | { 122 | firstLog = false; 123 | } 124 | } 125 | 126 | if (type != MessageType.log) 127 | { 128 | send(Window.logMessage, new LogMessageParams(type, format(logMessageFormat, 129 | Clock.currTime.toString(), messageSeverity[type], message))); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/dls/protocol/messages/client.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.messages.client; 22 | 23 | import std.json : JSONValue; 24 | 25 | void registerCapability(string id, JSONValue nothing) 26 | { 27 | } 28 | 29 | void unregisterCapability(string id, JSONValue nothing) 30 | { 31 | } 32 | -------------------------------------------------------------------------------- /source/dls/protocol/messages/methods.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.messages.methods; 22 | 23 | //dfmt off 24 | enum General : string 25 | { 26 | initialize = "initialize", 27 | initialized = "initialized", 28 | shutdown = "shutdown", 29 | exit = "exit", 30 | cancelRequest = "$/cancelRequest", 31 | } 32 | 33 | enum Window : string 34 | { 35 | showMessage = "window/showMessage", 36 | showMessageRequest = "window/showMessageRequest", 37 | logMessage = "window/logMessage", 38 | } 39 | 40 | enum Telemetry : string 41 | { 42 | event = "telemetry/event", 43 | } 44 | 45 | enum Client : string 46 | { 47 | registerCapability = "client/registerCapability", 48 | unregisterCapability = "client/unregisterCapability", 49 | } 50 | 51 | enum Workspace : string 52 | { 53 | workspaceFolders = "workspace/workspaceFolders", 54 | didChangeWorkspaceFolders = "workspace/didChangeWorkspaceFolders", 55 | configuration = "workspace/configuration", 56 | didChangeWatchedFiles = "workspace/didChangeWatchedFiles", 57 | symbol = "workspace/symbol", 58 | executeCommand = "workspace/executeCommand", 59 | applyEdit = "workspace/applyEdit", 60 | } 61 | 62 | enum TextDocument : string 63 | { 64 | didOpen = "textDocument/didOpen", 65 | didChange = "textDocument/didChange", 66 | willSave = "textDocument/willSave", 67 | willSaveWaitUntil = "textDocument/willSaveWaitUntil", 68 | didSave = "textDocument/didSave", 69 | didClose = "textDocument/didClose", 70 | publishDiagnostics = "textDocument/publishDiagnostics", 71 | completion = "textDocument/completion", 72 | completionResolve = "completionItem/resolve", 73 | hover = "textDocument/hover", 74 | signatureHelp = "textDocument/signatureHelp", 75 | declaration = "textDocument/declaration", 76 | definition = "textDocument/definition", 77 | typeDefinition = "textDocument/typeDefinition", 78 | implementation = "textDocument/implementation", 79 | references = "textDocument/references", 80 | documentHighlight = "textDocument/documentHighlight", 81 | documentSymbol = "textDocument/documentSymbol", 82 | codeAction = "textDocument/codeAction", 83 | codeLens = "textDocument/codeLens", 84 | codeLensResolve = "codeLens/resolve", 85 | documentLink = "textDocument/documentLink", 86 | documentLinkResolve = "documentLink/resolve", 87 | documentColor = "textDocument/documentColor", 88 | colorPresentation = "textDocument/colorPresentation", 89 | formatting = "textDocument/formatting", 90 | rangeFormatting = "textDocument/rangeFormatting", 91 | onTypeFormatting = "textDocument/onTypeFormatting", 92 | rename = "textDocument/rename", 93 | prepareRename = "textDocument/prepareRename", 94 | foldingRange = "textDocument/foldingRange", 95 | } 96 | 97 | interface Dls 98 | { 99 | enum UpgradeDls : string 100 | { 101 | didStart = "$/dls/upgradeDls/didStart", 102 | didStop = "$/dls/upgradeDls/didStop", 103 | didChangeTotalSize = "$/dls/upgradeDls/didChangeTotalSize", 104 | didChangeCurrentSize = "$/dls/upgradeDls/didChangeCurrentSize", 105 | didExtract = "$/dls/upgradeDls/didExtract", 106 | } 107 | 108 | enum UpgradeSelections : string 109 | { 110 | didStart = "$/dls/upgradeSelections/didStart", 111 | didStop = "$/dls/upgradeSelections/didStop", 112 | } 113 | } 114 | //dfmt on 115 | -------------------------------------------------------------------------------- /source/dls/protocol/messages/other.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.messages.other; 22 | 23 | import dls.protocol.interfaces.other; 24 | 25 | @("$") 26 | void setTraceNotification(SetTraceParams params) 27 | { 28 | import dls.protocol.logger : logger; 29 | 30 | logger.info("Setting trace level to: %s", params.value); 31 | logger.trace = params.value; 32 | } 33 | -------------------------------------------------------------------------------- /source/dls/protocol/messages/window.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.messages.window; 22 | 23 | import dls.protocol.interfaces : MessageActionItem; 24 | import std.typecons : Nullable; 25 | 26 | void showMessageRequest(string id, Nullable!MessageActionItem item) 27 | { 28 | import dls.protocol.logger : logger; 29 | import dls.tools.symbol_tool : SymbolTool; 30 | import dls.util.i18n : Tr, tr; 31 | import dls.util.uri : Uri; 32 | import std.concurrency : locate, receiveOnly, send; 33 | import std.process : browse; 34 | 35 | while (id !in Util.messageRequestInfo) 36 | { 37 | auto data = receiveOnly!(Util.ThreadMessageData)(); 38 | Util.bindMessageToRequestId(data[0], data[1], data[2]); 39 | } 40 | 41 | if (!item.isNull) 42 | { 43 | switch (Util.messageRequestInfo[id][0]) 44 | { 45 | case Tr.app_upgradeSelections: 46 | if (item.title == tr(Tr.app_upgradeSelections_upgrade)) 47 | { 48 | auto uri = new Uri(Util.messageRequestInfo[id][1]); 49 | SymbolTool.instance.upgradeSelections(uri); 50 | } 51 | 52 | break; 53 | 54 | case Tr.app_upgradeDls: 55 | send(locate(Util.messageRequestInfo[id][1]), 56 | item.title == tr(Tr.app_upgradeDls_upgrade)); 57 | break; 58 | 59 | case Tr.app_showChangelog: 60 | if (item.title == tr(Tr.app_showChangelog_show)) 61 | { 62 | logger.info("Opening changelog in browser"); 63 | browse(Util.messageRequestInfo[id][1]); 64 | } 65 | 66 | break; 67 | 68 | default: 69 | assert(false, Util.messageRequestInfo[id][0] ~ " cannot be handled as requests"); 70 | } 71 | } 72 | 73 | Util.messageRequestInfo.remove(id); 74 | } 75 | 76 | final abstract class Util 77 | { 78 | import dls.protocol.jsonrpc : send; 79 | import dls.protocol.messages.methods : Window; 80 | import dls.util.i18n : Tr, tr, trType; 81 | import std.array : array; 82 | import std.algorithm : map; 83 | import std.typecons : Tuple, tuple; 84 | 85 | shared alias ThreadMessageData = Tuple!(string, Tr, string); 86 | 87 | private static Tuple!(Tr, string)[string] messageRequestInfo; 88 | 89 | static void sendMessage(Tr message, string[] args = []) 90 | { 91 | import dls.protocol.interfaces : ShowMessageParams; 92 | 93 | send(Window.showMessage, new ShowMessageParams(trType(message), tr(message, args))); 94 | } 95 | 96 | static string sendMessageRequest(Tr message, Tr[] actions, string[] args = []) 97 | { 98 | import dls.protocol.interfaces : ShowMessageRequestParams; 99 | import std.typecons : nullable; 100 | 101 | return send(Window.showMessageRequest, new ShowMessageRequestParams(trType(message), 102 | tr(message, args), actions.map!(a => new MessageActionItem(tr(a, 103 | args))).array.nullable)); 104 | } 105 | 106 | static void bindMessageToRequestId(string id, Tr message, string data = null) 107 | { 108 | messageRequestInfo[id] = tuple(message, data); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /source/dls/protocol/messages/workspace.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.messages.workspace; 22 | 23 | import dls.protocol.interfaces : SymbolInformation; 24 | import dls.protocol.interfaces.workspace; 25 | import std.json : JSONValue; 26 | import std.typecons : Nullable; 27 | 28 | void workspaceFolders(string id, Nullable!(WorkspaceFolder[]) folders) 29 | { 30 | import dls.protocol.jsonrpc : send; 31 | import dls.protocol.messages.methods : Workspace; 32 | import dls.protocol.state : initState; 33 | import dls.tools.analysis_tool : AnalysisTool; 34 | import dls.tools.symbol_tool : SymbolTool; 35 | import dls.tools.tool : Tool; 36 | import dls.util.uri : Uri; 37 | import std.typecons : nullable; 38 | 39 | if (!folders.isNull) 40 | { 41 | ConfigurationItem[] items; 42 | 43 | foreach (workspaceFolder; folders) 44 | { 45 | auto uri = new Uri(workspaceFolder.uri); 46 | items ~= new ConfigurationItem(uri.toString().nullable); 47 | Tool.instance.updateConfig(uri, JSONValue()); 48 | SymbolTool.instance.importPath(uri); 49 | AnalysisTool.instance.addAnalysisConfig(uri); 50 | } 51 | 52 | immutable conf = !initState.capabilities.workspace.isNull 53 | && !initState.capabilities.workspace.configuration.isNull 54 | && initState.capabilities.workspace.configuration; 55 | 56 | if (conf) 57 | { 58 | send(Workspace.configuration, new ConfigurationParams(items)); 59 | } 60 | } 61 | } 62 | 63 | void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) 64 | { 65 | import dls.tools.analysis_tool : AnalysisTool; 66 | import dls.tools.symbol_tool : SymbolTool; 67 | import dls.tools.tool : Tool; 68 | import dls.util.uri : Uri; 69 | import std.typecons : nullable; 70 | 71 | workspaceFolders(null, params.event.added.nullable); 72 | 73 | foreach (folder; params.event.removed) 74 | { 75 | auto uri = new Uri(folder.uri); 76 | Tool.instance.removeConfig(uri); 77 | SymbolTool.instance.clearPath(uri); 78 | AnalysisTool.instance.removeAnalysisConfig(uri); 79 | } 80 | } 81 | 82 | void configuration(string id, JSONValue[] configs) 83 | { 84 | import dls.protocol.logger : logger; 85 | import dls.tools.tool : Tool; 86 | 87 | auto uris = null ~ Tool.instance.workspacesUris; 88 | 89 | logger.info("Updating workspace configurations"); 90 | 91 | for (size_t i; i < configs.length && i < uris.length; ++i) 92 | { 93 | auto config = configs[i]; 94 | 95 | if ("d" in config && "dls" in config["d"]) 96 | { 97 | Tool.instance.updateConfig(uris[i], config["d"]["dls"]); 98 | } 99 | } 100 | } 101 | 102 | void didChangeConfiguration(DidChangeConfigurationParams params) 103 | { 104 | import dls.protocol.jsonrpc : send; 105 | import dls.protocol.logger : logger; 106 | import dls.protocol.messages.methods : Workspace; 107 | import dls.protocol.state : initState; 108 | import dls.tools.configuration : Configuration; 109 | import dls.tools.tool : Tool; 110 | import std.typecons : Nullable, nullable; 111 | 112 | logger.info("Configuration changed"); 113 | 114 | immutable conf = !initState.capabilities.workspace.isNull 115 | && !initState.capabilities.workspace.configuration.isNull 116 | && initState.capabilities.workspace.configuration; 117 | 118 | if (conf) 119 | { 120 | auto items = [new ConfigurationItem(Nullable!string(null))]; 121 | 122 | foreach (uri; Tool.instance.workspacesUris) 123 | { 124 | items ~= new ConfigurationItem(uri.toString().nullable); 125 | } 126 | 127 | send(Workspace.configuration, new ConfigurationParams(items)); 128 | } 129 | else if ("d" in params.settings && "dls" in params.settings["d"]) 130 | { 131 | logger.info("Updating configuration"); 132 | Tool.instance.updateConfig(null, params.settings["d"]["dls"]); 133 | } 134 | } 135 | 136 | void didChangeWatchedFiles(DidChangeWatchedFilesParams params) 137 | { 138 | import dls.protocol.interfaces : FileChangeType, PublishDiagnosticsParams; 139 | import dls.protocol.jsonrpc : send; 140 | import dls.protocol.logger : logger; 141 | import dls.protocol.messages.methods : TextDocument; 142 | import dls.tools.analysis_tool : AnalysisTool; 143 | import dls.tools.symbol_tool : SymbolTool; 144 | import dls.util.document : Document; 145 | import dls.util.uri : Uri, sameFile; 146 | import std.algorithm : canFind, filter, startsWith; 147 | import std.file : exists, isFile; 148 | import std.path : baseName, dirName, extension, pathSplitter; 149 | 150 | foreach (event; params.changes.filter!(event => event.type == FileChangeType.deleted)) 151 | { 152 | auto workspaceUri = SymbolTool.instance.getWorkspace(new Uri(event.uri)); 153 | 154 | if (workspaceUri !is null && !exists(workspaceUri.path)) 155 | { 156 | SymbolTool.instance.clearPath(workspaceUri); 157 | } 158 | } 159 | 160 | outer: foreach (event; params.changes) 161 | { 162 | auto uri = new Uri(event.uri); 163 | auto dirPath = dirName(uri.path); 164 | 165 | foreach (part; pathSplitter(dirPath)) 166 | { 167 | if (part.startsWith('.')) 168 | { 169 | continue outer; 170 | } 171 | } 172 | 173 | auto dirUri = Uri.fromPath(dirPath); 174 | 175 | logger.info("Resource %s: %s", event.type, uri.path); 176 | 177 | if (exists(uri.path) && !isFile(uri.path)) 178 | { 179 | continue; 180 | } 181 | 182 | switch (baseName(uri.path)) 183 | { 184 | case "dub.json", "dub.sdl": 185 | if (event.type != FileChangeType.deleted) 186 | { 187 | SymbolTool.instance.importDubProject(dirUri); 188 | } 189 | 190 | continue; 191 | 192 | case "dub.selections.json": 193 | if (event.type != FileChangeType.deleted) 194 | { 195 | SymbolTool.instance.importDubSelections(dirUri); 196 | } 197 | 198 | continue; 199 | 200 | case ".gitmodules": 201 | if (event.type != FileChangeType.deleted) 202 | { 203 | SymbolTool.instance.importGitSubmodules(dirUri); 204 | } 205 | 206 | continue; 207 | 208 | default: 209 | break; 210 | } 211 | 212 | switch (extension(uri.path)) 213 | { 214 | case ".d", ".di": 215 | Uri[] discarded; 216 | 217 | if (event.type == FileChangeType.deleted) 218 | { 219 | send(TextDocument.publishDiagnostics, new PublishDiagnosticsParams(uri, [])); 220 | } 221 | else if (!Document.uris.canFind!sameFile(uri) 222 | && AnalysisTool.instance.getScannableFilesUris(discarded) 223 | .canFind!sameFile(uri)) 224 | { 225 | send(TextDocument.publishDiagnostics, new PublishDiagnosticsParams(uri, 226 | AnalysisTool.instance.diagnostics(uri))); 227 | } 228 | 229 | continue; 230 | 231 | case ".ini": 232 | AnalysisTool.instance.updateAnalysisConfig(dirUri); 233 | continue; 234 | 235 | default: 236 | break; 237 | } 238 | } 239 | } 240 | 241 | SymbolInformation[] symbol(WorkspaceSymbolParams params) 242 | { 243 | import dls.tools.symbol_tool : SymbolTool; 244 | 245 | return SymbolTool.instance.symbol(params.query); 246 | } 247 | 248 | JSONValue executeCommand(ExecuteCommandParams params) 249 | { 250 | import dls.tools.command_tool : CommandTool; 251 | 252 | return CommandTool.instance.executeCommand(params.command, 253 | params.arguments.isNull ? [] : params.arguments.get()); 254 | } 255 | 256 | void applyEdit(string id, ApplyWorkspaceEditResponse response) 257 | { 258 | } 259 | -------------------------------------------------------------------------------- /source/dls/protocol/state.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.protocol.state; 22 | 23 | import dls.protocol.interfaces : InitializeParams; 24 | 25 | private InitializeParams _initState; 26 | private InitializeParams.InitializationOptions _commandLineOptions; 27 | 28 | static this() 29 | { 30 | _initState = new InitializeParams(); 31 | _commandLineOptions = new InitializeParams.InitializationOptions(); 32 | } 33 | 34 | @property InitializeParams initState() 35 | { 36 | return _initState; 37 | } 38 | 39 | @property void initState(InitializeParams params) 40 | { 41 | import dls.protocol.logger : logger; 42 | import dls.util.disposable_fiber : DisposableFiber; 43 | 44 | assert(params !is null); 45 | _initState = params; 46 | 47 | if (!params.trace.isNull) 48 | { 49 | logger.trace = params.trace; 50 | } 51 | 52 | if (_initState.initializationOptions.isNull) 53 | { 54 | _initState.initializationOptions = _commandLineOptions; 55 | } 56 | else 57 | { 58 | merge(params.initializationOptions.get(), _commandLineOptions); 59 | } 60 | 61 | DisposableFiber.safeMode = initOptions.safeMode; 62 | } 63 | 64 | @property InitializeParams.InitializationOptions initOptions() 65 | { 66 | return initState.initializationOptions.isNull ? _commandLineOptions 67 | : _initState.initializationOptions; 68 | } 69 | 70 | @property void initOptions(InitializeParams.InitializationOptions options) 71 | { 72 | assert(options !is null); 73 | _commandLineOptions = options; 74 | } 75 | 76 | private void merge(T)(ref T options, const T addins) 77 | { 78 | import std.meta : Alias; 79 | import std.traits : isSomeFunction, isType; 80 | import dls.protocol.logger : logger; 81 | 82 | static if (is(T == class)) 83 | { 84 | immutable reference = new T(); 85 | } 86 | else 87 | { 88 | immutable reference = T.init; 89 | } 90 | 91 | foreach (member; __traits(allMembers, T)) 92 | { 93 | alias m = Alias!(__traits(getMember, T, member)); 94 | alias optionsMember = Alias!(mixin("options." ~ member)); 95 | alias addinsMember = Alias!(mixin("addins." ~ member)); 96 | 97 | static if (__traits(getProtection, m) != "public" || isType!m || isSomeFunction!m) 98 | { 99 | continue; 100 | } 101 | else static if (is(typeof(m) == class)) 102 | { 103 | merge(mixin("options." ~ member), mixin("addins." ~ member)); 104 | } 105 | else 106 | { 107 | if (mixin("options." ~ member) == mixin("reference." ~ member)) 108 | { 109 | mixin("options." ~ member) = mixin("addins." ~ member); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /source/dls/tools/command_tool.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.command_tool; 22 | 23 | import dls.tools.tool : Tool; 24 | 25 | enum Commands : string 26 | { 27 | workspaceEdit = "workspaceEdit", 28 | codeAction_analysis_disableCheck = "codeAction.analysis.disableCheck" 29 | } 30 | 31 | class CommandTool : Tool 32 | { 33 | import std.json : JSONValue; 34 | 35 | private static CommandTool _instance; 36 | 37 | static void initialize(CommandTool tool) 38 | { 39 | _instance = tool; 40 | } 41 | 42 | static void shutdown() 43 | { 44 | destroy(_instance); 45 | } 46 | 47 | @property static CommandTool instance() 48 | { 49 | return _instance; 50 | } 51 | 52 | @property string[] commands() 53 | { 54 | string[] result; 55 | 56 | foreach (member; __traits(allMembers, Commands)) 57 | { 58 | result ~= mixin("Commands." ~ member); 59 | } 60 | 61 | return result; 62 | } 63 | 64 | JSONValue executeCommand(const string commandName, const JSONValue[] arguments) 65 | { 66 | import dls.protocol.definitions : WorkspaceEdit; 67 | import dls.protocol.errors : InvalidParamsException; 68 | import dls.protocol.interfaces : ApplyWorkspaceEditParams; 69 | import dls.protocol.jsonrpc : send; 70 | import dls.protocol.logger : logger; 71 | import dls.protocol.messages.methods : Workspace; 72 | import dls.tools.analysis_tool : AnalysisTool; 73 | import dls.util.json : convertFromJSON; 74 | import dls.util.uri : Uri; 75 | import std.json : JSONException; 76 | import std.format : format; 77 | 78 | logger.info("Executing command %s with arguments %s", commandName, arguments); 79 | 80 | try 81 | { 82 | final switch (convertFromJSON!Commands(JSONValue(commandName))) 83 | { 84 | case Commands.workspaceEdit: 85 | send(Workspace.applyEdit, 86 | new ApplyWorkspaceEditParams(convertFromJSON!WorkspaceEdit(arguments[0]))); 87 | break; 88 | 89 | case Commands.codeAction_analysis_disableCheck: 90 | AnalysisTool.instance.disableCheck(new Uri(convertFromJSON!string(arguments[0])), 91 | convertFromJSON!string(arguments[1])); 92 | break; 93 | } 94 | } 95 | catch (JSONException e) 96 | { 97 | throw new InvalidParamsException(format!"unknown command: %s"(commandName)); 98 | } 99 | 100 | return JSONValue(null); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /source/dls/tools/configuration.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.configuration; 22 | 23 | class Configuration 24 | { 25 | import std.json : JSONValue; 26 | 27 | static class SymbolConfiguration 28 | { 29 | string[] importPaths; 30 | bool listLocalSymbols; 31 | } 32 | 33 | static class AnalysisConfiguration 34 | { 35 | string configFile = "dscanner.ini"; 36 | string[] filePatterns = []; 37 | } 38 | 39 | static class FormatConfiguration 40 | { 41 | static enum Engine : string 42 | { 43 | dfmt = "dfmt", 44 | builtin = "builtin" 45 | } 46 | 47 | static enum BraceStyle : string 48 | { 49 | allman = "allman", 50 | otbs = "otbs", 51 | stroustrup = "stroustrup" 52 | } 53 | 54 | static enum EndOfLine : string 55 | { 56 | lf = "lf", 57 | cr = "cr", 58 | crlf = "crlf" 59 | } 60 | 61 | static enum TemplateConstraintsStyle : string 62 | { 63 | conditionalNewlineIndent = "conditionalNewlineIndent", 64 | conditionalNewline = "conditionalNewline", 65 | alwaysNewline = "alwaysNewline", 66 | alwaysNewlineIndent = "alwaysNewlineIndent" 67 | } 68 | 69 | Engine engine = Engine.dfmt; 70 | EndOfLine endOfLine = EndOfLine.lf; 71 | bool insertFinalNewline = true; 72 | bool trimTrailingWhitespace = true; 73 | int maxLineLength = 120; 74 | int softMaxLineLength = 80; 75 | BraceStyle braceStyle = BraceStyle.allman; 76 | bool spaceAfterCasts = true; 77 | bool spaceAfterKeywords = true; 78 | bool spaceBeforeAAColons = false; 79 | bool spaceBeforeFunctionParameters = false; 80 | bool spaceBeforeSelectiveImportColons = true; 81 | bool alignSwitchStatements = true; 82 | bool compactLabeledStatements = true; 83 | bool outdentAttributes = true; 84 | bool splitOperatorsAtLineEnd = false; 85 | TemplateConstraintsStyle templateConstraintsStyle = TemplateConstraintsStyle 86 | .conditionalNewlineIndent; 87 | bool templateConstraintsSingleIndent = false; 88 | } 89 | 90 | SymbolConfiguration symbol; 91 | AnalysisConfiguration analysis; 92 | FormatConfiguration format; 93 | 94 | this() 95 | { 96 | symbol = new SymbolConfiguration(); 97 | analysis = new AnalysisConfiguration(); 98 | format = new FormatConfiguration(); 99 | } 100 | 101 | void merge(JSONValue json) 102 | { 103 | merge!(typeof(this))(json); 104 | } 105 | 106 | private void merge(T)(JSONValue json) 107 | { 108 | import dls.util.json : convertFromJSON; 109 | import std.json : JSONType; 110 | import std.meta : Alias; 111 | import std.traits : isSomeFunction, isType; 112 | 113 | if (json.type != JSONType.object) 114 | { 115 | return; 116 | } 117 | 118 | foreach (member; __traits(allMembers, T)) 119 | { 120 | if (member !in json) 121 | { 122 | continue; 123 | } 124 | 125 | alias m = Alias!(__traits(getMember, T, member)); 126 | 127 | static if (!isType!(m) && !isSomeFunction!(m)) 128 | { 129 | static if (is(m == class)) 130 | { 131 | merge(m, json[member]); 132 | } 133 | else 134 | { 135 | m = convertFromJSON!(typeof(m))(json[member]); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /source/dls/tools/format_tool/internal/dfmt_format_tool.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.format_tool.internal.dfmt_format_tool; 22 | 23 | import dls.tools.format_tool.internal.format_tool : FormatTool; 24 | 25 | private immutable configPattern = "dummy.d"; 26 | 27 | class DfmtFormatTool : FormatTool 28 | { 29 | import dfmt.config : Config; 30 | import dls.protocol.definitions : Position, Range, TextEdit; 31 | import dls.protocol.interfaces : FormattingOptions; 32 | import dls.util.uri : Uri; 33 | 34 | override TextEdit[] formatting(const Uri uri, const FormattingOptions options) 35 | { 36 | import dfmt.formatter : format; 37 | import dls.protocol.logger : logger; 38 | import dls.util.document : Document; 39 | import std.outbuffer : OutBuffer; 40 | 41 | logger.info("Formatting %s", uri.path); 42 | 43 | const document = Document.get(uri); 44 | auto contents = cast(ubyte[]) document.toString(); 45 | auto config = getFormatConfig(uri, options); 46 | auto buffer = new OutBuffer(); 47 | format(uri.path, contents, buffer, &config); 48 | return diff(uri, buffer.toString()); 49 | } 50 | 51 | private Config getFormatConfig(const Uri uri, const FormattingOptions options) 52 | { 53 | import dfmt.config : BraceStyle, TemplateConstraintStyle; 54 | import dfmt.editorconfig : EOL, IndentStyle, OptionalBoolean, getConfigFor; 55 | import dls.tools.configuration : Configuration; 56 | import dls.tools.symbol_tool : SymbolTool; 57 | 58 | static OptionalBoolean toOptBool(bool b) 59 | { 60 | return b ? OptionalBoolean.t : OptionalBoolean.f; 61 | } 62 | 63 | auto formatConf = getConfig(SymbolTool.instance.getWorkspace(uri)).format; 64 | Config config; 65 | config.initializeWithDefaults(); 66 | config.pattern = configPattern; 67 | config.indent_style = options.insertSpaces ? IndentStyle.space : IndentStyle.tab; 68 | config.indent_size = cast(typeof(config.indent_size)) options.tabSize; 69 | config.tab_width = config.indent_size; 70 | config.max_line_length = formatConf.maxLineLength; 71 | config.dfmt_align_switch_statements = toOptBool(formatConf.alignSwitchStatements); 72 | config.dfmt_outdent_attributes = toOptBool(formatConf.outdentAttributes); 73 | config.dfmt_soft_max_line_length = formatConf.softMaxLineLength; 74 | config.dfmt_space_after_cast = toOptBool(formatConf.spaceAfterCasts); 75 | config.dfmt_space_after_keywords = toOptBool(formatConf.spaceAfterKeywords); 76 | config.dfmt_space_before_function_parameters = toOptBool( 77 | formatConf.spaceBeforeFunctionParameters); 78 | config.dfmt_split_operator_at_line_end = toOptBool(formatConf.splitOperatorsAtLineEnd); 79 | config.dfmt_selective_import_space = toOptBool(formatConf.spaceBeforeSelectiveImportColons); 80 | config.dfmt_compact_labeled_statements = formatConf.compactLabeledStatements 81 | ? OptionalBoolean.t : OptionalBoolean.f; 82 | config.dfmt_single_template_constraint_indent = toOptBool( 83 | formatConf.templateConstraintsSingleIndent); 84 | 85 | final switch (formatConf.endOfLine) 86 | { 87 | case Configuration.FormatConfiguration.EndOfLine.lf: 88 | config.end_of_line = EOL.lf; 89 | break; 90 | case Configuration.FormatConfiguration.EndOfLine.cr: 91 | config.end_of_line = EOL.cr; 92 | break; 93 | case Configuration.FormatConfiguration.EndOfLine.crlf: 94 | config.end_of_line = EOL.crlf; 95 | break; 96 | } 97 | 98 | final switch (formatConf.braceStyle) 99 | { 100 | case Configuration.FormatConfiguration.BraceStyle.allman: 101 | config.dfmt_brace_style = BraceStyle.allman; 102 | break; 103 | case Configuration.FormatConfiguration.BraceStyle.otbs: 104 | config.dfmt_brace_style = BraceStyle.otbs; 105 | break; 106 | case Configuration.FormatConfiguration.BraceStyle.stroustrup: 107 | config.dfmt_brace_style = BraceStyle.stroustrup; 108 | break; 109 | } 110 | 111 | final switch (formatConf.templateConstraintsStyle) 112 | { 113 | case Configuration.FormatConfiguration.TemplateConstraintsStyle.conditionalNewlineIndent: 114 | config.dfmt_template_constraint_style 115 | = TemplateConstraintStyle.conditional_newline_indent; 116 | break; 117 | case Configuration.FormatConfiguration.TemplateConstraintsStyle.conditionalNewline: 118 | config.dfmt_template_constraint_style = TemplateConstraintStyle.conditional_newline; 119 | break; 120 | case Configuration.FormatConfiguration.TemplateConstraintsStyle.alwaysNewline: 121 | config.dfmt_template_constraint_style = TemplateConstraintStyle.always_newline; 122 | break; 123 | case Configuration.FormatConfiguration.TemplateConstraintsStyle.alwaysNewlineIndent: 124 | config.dfmt_template_constraint_style = TemplateConstraintStyle.always_newline_indent; 125 | break; 126 | } 127 | 128 | auto fileConfig = getConfigFor!Config(uri.path); 129 | fileConfig.pattern = configPattern; 130 | config.merge(fileConfig, configPattern); 131 | return config; 132 | } 133 | 134 | private TextEdit[] diff(const Uri uri, const string after) 135 | { 136 | import dls.util.document : Document; 137 | import std.ascii : isWhite; 138 | import std.utf : decode; 139 | 140 | const document = Document.get(uri); 141 | immutable before = document.toString(); 142 | size_t i; 143 | size_t j; 144 | TextEdit[] result; 145 | 146 | size_t startIndex; 147 | size_t stopIndex; 148 | string text; 149 | 150 | bool pushTextEdit() 151 | { 152 | if (startIndex != stopIndex || text.length > 0) 153 | { 154 | result ~= new TextEdit(new Range(document.positionAtByte(startIndex), 155 | document.positionAtByte(stopIndex)), text); 156 | return true; 157 | } 158 | 159 | return false; 160 | } 161 | 162 | while (i < before.length || j < after.length) 163 | { 164 | auto newI = i; 165 | auto newJ = j; 166 | dchar beforeChar; 167 | dchar afterChar; 168 | 169 | if (newI < before.length) 170 | { 171 | beforeChar = decode(before, newI); 172 | } 173 | 174 | if (newJ < after.length) 175 | { 176 | afterChar = decode(after, newJ); 177 | } 178 | 179 | if (i < before.length && j < after.length && beforeChar == afterChar) 180 | { 181 | i = newI; 182 | j = newJ; 183 | 184 | if (pushTextEdit()) 185 | { 186 | startIndex = stopIndex; 187 | text = ""; 188 | } 189 | } 190 | 191 | if (startIndex == stopIndex) 192 | { 193 | startIndex = i; 194 | stopIndex = i; 195 | } 196 | 197 | auto addition = !isWhite(beforeChar) && isWhite(afterChar); 198 | immutable deletion = isWhite(beforeChar) && !isWhite(afterChar); 199 | 200 | if (!addition && !deletion) 201 | { 202 | addition = before.length - i < after.length - j; 203 | } 204 | 205 | if (addition && j < after.length) 206 | { 207 | text ~= after[j .. newJ]; 208 | j = newJ; 209 | } 210 | else if (i < before.length) 211 | { 212 | stopIndex = newI; 213 | i = newI; 214 | } 215 | } 216 | 217 | pushTextEdit(); 218 | return result; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /source/dls/tools/format_tool/internal/format_tool.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.format_tool.internal.format_tool; 22 | 23 | import dls.protocol.definitions : Range; 24 | import dls.tools.tool : Tool; 25 | 26 | abstract class FormatTool : Tool 27 | { 28 | import dls.protocol.definitions : Position, TextEdit; 29 | import dls.protocol.interfaces : FormattingOptions; 30 | import dls.util.uri : Uri; 31 | 32 | private static FormatTool _instance; 33 | 34 | static void initialize(FormatTool tool) 35 | { 36 | import dls.tools.configuration : Configuration; 37 | import dls.tools.format_tool.internal.builtin_format_tool : BuiltinFormatTool; 38 | import dls.tools.format_tool.internal.dfmt_format_tool : DfmtFormatTool; 39 | 40 | _instance = tool; 41 | _instance.addConfigHook("engine", (const Uri uri) { 42 | auto config = getConfig(null); 43 | 44 | if (config.format.engine == Configuration.FormatConfiguration.Engine.dfmt 45 | && typeid(_instance) == typeid(DfmtFormatTool)) 46 | { 47 | return; 48 | } 49 | 50 | FormatTool.shutdown(); 51 | FormatTool.initialize(config.format.engine == Configuration.FormatConfiguration.Engine.dfmt 52 | ? new DfmtFormatTool() : new BuiltinFormatTool()); 53 | }); 54 | } 55 | 56 | static void shutdown() 57 | { 58 | _instance.removeConfigHooks(); 59 | destroy(_instance); 60 | } 61 | 62 | @property static FormatTool instance() 63 | { 64 | return _instance; 65 | } 66 | 67 | TextEdit[] formatting(const Uri uri, const FormattingOptions options); 68 | 69 | TextEdit[] rangeFormatting(const Uri uri, const Range range, const FormattingOptions options) 70 | { 71 | import dls.util.document : Document; 72 | import std.algorithm : filter; 73 | import std.array : array; 74 | 75 | const document = Document.get(uri); 76 | document.validatePosition(range.start); 77 | return formatting(uri, options).filter!((edit) => edit.range.isValidEditFor(range)).array; 78 | } 79 | 80 | TextEdit[] onTypeFormatting(const Uri uri, const Position position, 81 | const FormattingOptions options) 82 | { 83 | import dls.util.document : Document; 84 | import std.algorithm : filter; 85 | import std.array : array; 86 | import std.string : stripRight; 87 | 88 | const document = Document.get(uri); 89 | document.validatePosition(position); 90 | 91 | if (position.character != stripRight(document.lines[position.line]).length) 92 | { 93 | return []; 94 | } 95 | 96 | return formatting(uri, options).filter!(edit => edit.range.start.line == position.line 97 | || edit.range.end.line == position.line).array; 98 | } 99 | } 100 | 101 | package bool isValidEditFor(const Range editRange, const Range formatRange) 102 | { 103 | //dfmt off 104 | return (editRange.start.line < formatRange.end.line 105 | || (editRange.start.line == formatRange.end.line && editRange.start.character <= formatRange.end.character)) 106 | && (editRange.end.line > formatRange.start.line 107 | || (editRange.end.line == formatRange.start.line && editRange.end.character >= formatRange.start.character)); 108 | //dfmt on 109 | } 110 | -------------------------------------------------------------------------------- /source/dls/tools/format_tool/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.format_tool; 22 | 23 | public import dls.tools.format_tool.internal.builtin_format_tool; 24 | public import dls.tools.format_tool.internal.dfmt_format_tool; 25 | public import dls.tools.format_tool.internal.format_tool; 26 | -------------------------------------------------------------------------------- /source/dls/tools/symbol_tool/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.symbol_tool; 22 | 23 | public import dls.tools.symbol_tool.internal.symbol_tool; 24 | -------------------------------------------------------------------------------- /source/dls/tools/tool.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.tools.tool; 22 | 23 | import dls.util.uri : Uri; 24 | 25 | alias Hook = void delegate(const Uri uri); 26 | 27 | class Tool 28 | { 29 | import dls.tools.configuration : Configuration; 30 | import std.json : JSONValue; 31 | 32 | private static Tool _instance; 33 | private static Configuration _globalConfig; 34 | private static JSONValue[string] _workspacesConfigs; 35 | private static Hook[string] _configHooks; 36 | 37 | @property Uri[] workspacesUris() 38 | { 39 | import std.algorithm : map, sort; 40 | import std.array : array; 41 | 42 | return _workspacesConfigs.keys.sort().map!(u => Uri.fromPath(u)).array; 43 | } 44 | 45 | static void initialize(Tool tool) 46 | { 47 | _instance = tool; 48 | _globalConfig = new Configuration(); 49 | } 50 | 51 | static void shutdown() 52 | { 53 | _globalConfig = new Configuration(); 54 | _workspacesConfigs.clear(); 55 | _configHooks.clear(); 56 | } 57 | 58 | protected static Configuration getConfig(const Uri uri) 59 | { 60 | import dls.util.json : convertToJSON; 61 | 62 | if (uri is null || uri.path !in _workspacesConfigs) 63 | { 64 | return _globalConfig; 65 | } 66 | 67 | auto config = new Configuration(); 68 | config.merge(convertToJSON(_globalConfig)); 69 | config.merge(_workspacesConfigs[uri.path]); 70 | return config; 71 | } 72 | 73 | @property static Tool instance() 74 | { 75 | return _instance; 76 | } 77 | 78 | void updateConfig(const Uri uri, JSONValue json) 79 | { 80 | import dls.protocol.state : initState; 81 | 82 | if (uri is null || uri.path.length == 0) 83 | { 84 | _globalConfig.merge(json); 85 | } 86 | else 87 | { 88 | _workspacesConfigs[uri.path] = json; 89 | } 90 | 91 | foreach (hook; _configHooks.byValue) 92 | { 93 | hook(uri is null ? initState.rootUri.isNull ? null : new Uri(initState.rootUri) : uri); 94 | } 95 | } 96 | 97 | void removeConfig(const Uri uri) 98 | { 99 | if (uri in _workspacesConfigs) 100 | { 101 | _workspacesConfigs.remove(uri.path); 102 | } 103 | } 104 | 105 | protected void addConfigHook(string name, Hook hook) 106 | { 107 | _configHooks[this.toString() ~ '/' ~ name] = hook; 108 | } 109 | 110 | protected void removeConfigHooks() 111 | { 112 | import std.algorithm : startsWith; 113 | 114 | foreach (key; _configHooks.byKey) 115 | { 116 | if (key.startsWith(this.toString() ~ '/')) 117 | { 118 | _configHooks.remove(key); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /source/dls/updater.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.updater; 22 | 23 | private immutable changelogUrl = "https://github.com/d-language-server/dls/blob/v%s/CHANGELOG.md"; 24 | 25 | void cleanup() 26 | { 27 | import dls.bootstrap : dubBinDir; 28 | import dls.info : currentVersion; 29 | import dub.semver : compareVersions; 30 | import std.file : FileException, SpanMode, dirEntries, isSymlink, remove, rmdirRecurse; 31 | import std.path : baseName; 32 | import std.regex : matchFirst; 33 | 34 | foreach (string entry; dirEntries(dubBinDir, SpanMode.shallow)) 35 | { 36 | const match = entry.baseName.matchFirst(`dls-v([\d.]+)`); 37 | 38 | if (match) 39 | { 40 | if (compareVersions(currentVersion, match[1]) > 0) 41 | { 42 | try 43 | { 44 | rmdirRecurse(entry); 45 | } 46 | catch (FileException e) 47 | { 48 | } 49 | } 50 | } 51 | else if (isSymlink(entry)) 52 | { 53 | try 54 | { 55 | version (Windows) 56 | { 57 | import std.file : isDir, rmdir; 58 | import std.stdio : File; 59 | 60 | if (isDir(entry)) 61 | { 62 | try 63 | { 64 | dirEntries(entry, SpanMode.shallow); 65 | } 66 | catch (FileException e) 67 | { 68 | rmdir(entry); 69 | } 70 | } 71 | else 72 | { 73 | try 74 | { 75 | File(entry, "rb"); 76 | } 77 | catch (Exception e) 78 | { 79 | remove(entry); 80 | } 81 | } 82 | } 83 | else version (Posix) 84 | { 85 | import std.file : exists, readLink; 86 | 87 | if (!exists(readLink(entry))) 88 | { 89 | remove(entry); 90 | } 91 | } 92 | else 93 | { 94 | static assert(false, "Platform not supported"); 95 | } 96 | } 97 | catch (Exception e) 98 | { 99 | } 100 | } 101 | } 102 | } 103 | 104 | void update(bool autoUpdate, bool preReleaseBuilds) 105 | { 106 | import core.time : hours; 107 | import dls.bootstrap : UpgradeFailedException, canDownloadDls, downloadDls, 108 | allReleases, linkDls; 109 | import dls.info : currentVersion; 110 | static import dls.protocol.jsonrpc; 111 | import dls.protocol.interfaces.dls : DlsUpgradeSizeParams, TranslationParams; 112 | import dls.protocol.logger : logger; 113 | import dls.protocol.messages.methods : Dls; 114 | import dls.protocol.messages.window : Util; 115 | import dls.util.i18n : Tr; 116 | import dub.semver : compareVersions; 117 | import std.algorithm : filter, stripLeft; 118 | import std.concurrency : ownerTid, receiveOnly, register, send, thisTid; 119 | import std.datetime : Clock, SysTime; 120 | import std.format : format; 121 | import std.json : JSONType; 122 | 123 | auto validReleases = allReleases.filter!( 124 | r => r["prerelease"].type == JSONType.false_ || preReleaseBuilds); 125 | 126 | if (validReleases.empty) 127 | { 128 | logger.warning("Unable to find any valid release"); 129 | return; 130 | } 131 | 132 | immutable latestRelease = validReleases.front; 133 | immutable latestVersion = latestRelease["tag_name"].str.stripLeft('v'); 134 | immutable releaseTime = SysTime.fromISOExtString(latestRelease["published_at"].str); 135 | 136 | if (latestVersion.length == 0 || compareVersions(currentVersion, 137 | latestVersion) >= 0 || (Clock.currTime.toUTC() - releaseTime < 1.hours)) 138 | { 139 | return; 140 | } 141 | 142 | if (!autoUpdate) 143 | { 144 | auto id = Util.sendMessageRequest(Tr.app_upgradeDls, 145 | [Tr.app_upgradeDls_upgrade], [latestVersion, currentVersion]); 146 | immutable threadName = "updater"; 147 | register(threadName, thisTid()); 148 | send(ownerTid(), Util.ThreadMessageData(id, Tr.app_upgradeDls, threadName)); 149 | 150 | immutable shouldUpgrade = receiveOnly!bool(); 151 | 152 | if (!shouldUpgrade) 153 | { 154 | return; 155 | } 156 | } 157 | 158 | dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStart, 159 | new TranslationParams(Tr.app_upgradeDls_upgrading)); 160 | 161 | scope (exit) 162 | { 163 | dls.protocol.jsonrpc.send(Dls.UpgradeDls.didStop); 164 | } 165 | 166 | bool upgradeSuccessful; 167 | 168 | if (canDownloadDls) 169 | { 170 | try 171 | { 172 | enum totalSizeCallback = (size_t size) { 173 | dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeTotalSize, 174 | new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 175 | }; 176 | enum chunkSizeCallback = (size_t size) { 177 | dls.protocol.jsonrpc.send(Dls.UpgradeDls.didChangeCurrentSize, 178 | new DlsUpgradeSizeParams(Tr.app_upgradeDls_downloading, [], size)); 179 | }; 180 | enum extractCallback = () { 181 | dls.protocol.jsonrpc.send(Dls.UpgradeDls.didExtract, 182 | new TranslationParams(Tr.app_upgradeDls_extracting)); 183 | }; 184 | 185 | downloadDls(totalSizeCallback, chunkSizeCallback, extractCallback); 186 | upgradeSuccessful = true; 187 | } 188 | catch (Exception e) 189 | { 190 | logger.error("Could not download DLS: %s", e.msg); 191 | Util.sendMessage(Tr.app_upgradeDls_downloadError); 192 | } 193 | } 194 | 195 | try 196 | { 197 | linkDls(); 198 | auto id = Util.sendMessageRequest(Tr.app_showChangelog, 199 | [Tr.app_showChangelog_show], [latestVersion]); 200 | send(ownerTid(), Util.ThreadMessageData(id, Tr.app_showChangelog, 201 | format!changelogUrl(latestVersion))); 202 | } 203 | catch (UpgradeFailedException e) 204 | { 205 | logger.error("Could not symlink DLS: %s", e.msg); 206 | Util.sendMessage(Tr.app_upgradeDls_linkError); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /source/main.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | int main(string[] args) 22 | { 23 | import dls.bootstrap : networkBackend; 24 | import dls.info : buildArch, buildPlatform, buildType, compilerVersion, currentVersion; 25 | import dls.protocol.interfaces : InitializeParams; 26 | import dls.protocol.state : initOptions; 27 | import dls.server : Server; 28 | import dls.util.communicator : SocketCommunicator, StdioCommunicator, communicator; 29 | import dls.util.getopt : printHelp; 30 | import dls.util.i18n : Tr, tr; 31 | import std.ascii : newline; 32 | import std.compiler : name; 33 | import std.conv : text; 34 | import std.getopt : config, getopt; 35 | 36 | bool stdio = true; 37 | ushort port; 38 | bool version_; 39 | auto init = new InitializeParams.InitializationOptions(); 40 | 41 | //dfmt off 42 | auto result = getopt(args, config.passThrough, 43 | "stdio", tr(Tr.app_help_stdio), &stdio, 44 | "socket", tr(Tr.app_help_socket), &port, 45 | "tcp", tr(Tr.app_help_socket), &port, 46 | "version", tr(Tr.app_help_version), &version_, 47 | "init.autoUpdate", &init.autoUpdate, 48 | "init.preReleaseBuilds", &init.preReleaseBuilds, 49 | "init.safeMode", &init.safeMode, 50 | "init.catchErrors", &init.catchErrors, 51 | "init.logFile", &init.logFile, 52 | "init.capabilities.hover", &init.capabilities.hover, 53 | "init.capabilities.completion", &init.capabilities.completion, 54 | "init.capabilities.definition", &init.capabilities.definition, 55 | "init.capabilities.typeDefinition", &init.capabilities.typeDefinition, 56 | "init.capabilities.references", &init.capabilities.references, 57 | "init.capabilities.documentHighlight", &init.capabilities.documentHighlight, 58 | "init.capabilities.documentSymbol", &init.capabilities.documentSymbol, 59 | "init.capabilities.workspaceSymbol", &init.capabilities.workspaceSymbol, 60 | "init.capabilities.codeAction", &init.capabilities.codeAction, 61 | "init.capabilities.documentFormatting", &init.capabilities.documentFormatting, 62 | "init.capabilities.documentRangeFormatting", &init.capabilities.documentRangeFormatting, 63 | "init.capabilities.documentOnTypeFormatting", &init.capabilities.documentOnTypeFormatting, 64 | "init.capabilities.rename", &init.capabilities.rename, 65 | "init.symbol.autoImports", &init.symbol.autoImports); 66 | //dfmt on 67 | 68 | initOptions = init; 69 | 70 | if (result.helpWanted) 71 | { 72 | communicator = new StdioCommunicator(false); 73 | printHelp(tr(Tr.app_help_title), result.options, data => communicator.write(data)); 74 | communicator.flush(); 75 | return 0; 76 | } 77 | else if (version_) 78 | { 79 | communicator = new StdioCommunicator(false); 80 | communicator.write(tr(Tr.app_version_dlsVersion, [currentVersion, 81 | buildPlatform, buildArch, buildType, networkBackend])); 82 | communicator.write(newline); 83 | communicator.write(tr(Tr.app_version_compilerVersion, [name, 84 | compilerVersion, text(__VERSION__)])); 85 | communicator.write(newline); 86 | communicator.flush(); 87 | return 0; 88 | } 89 | else if (port > 0) 90 | { 91 | communicator = new SocketCommunicator(port); 92 | } 93 | else if (stdio) 94 | { 95 | communicator = new StdioCommunicator(true); 96 | } 97 | else 98 | { 99 | return -1; 100 | } 101 | 102 | Server.loop(); 103 | return Server.initialized ? 1 : 0; 104 | } 105 | -------------------------------------------------------------------------------- /tests/.gitattributes: -------------------------------------------------------------------------------- 1 | */source/*.d eol=lf 2 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.out.json 2 | -------------------------------------------------------------------------------- /tests/formatting/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formatting" 3 | } -------------------------------------------------------------------------------- /tests/formatting/messages/_order.txt: -------------------------------------------------------------------------------- 1 | initialize 2 | initialized 3 | textDocument_formatting 4 | textDocument_rangeFormatting 5 | textDocument_onTypeFormatting 6 | shutdown 7 | -------------------------------------------------------------------------------- /tests/formatting/messages/initialize.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "initialize", 4 | "method": "initialize", 5 | "params": { 6 | "processId": null, 7 | "rootUri": "testFile:///", 8 | "capabilities": { 9 | "textDocument": { 10 | "formatting": { 11 | "dynamicRegistration": false 12 | }, 13 | "rangeFormatting": { 14 | "dynamicRegistration": false 15 | }, 16 | "onTypeFormatting": { 17 | "dynamicRegistration": false 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/formatting/messages/initialize.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "initialize", 4 | "jsonrpc": "2.0", 5 | "result": { 6 | "capabilities": { 7 | "documentFormattingProvider": true, 8 | "documentOnTypeFormattingProvider": { 9 | "firstTriggerCharacter": ";" 10 | }, 11 | "documentRangeFormattingProvider": true, 12 | "textDocumentSync": { 13 | "change": 2, 14 | "openClose": true, 15 | "save": { 16 | "includeText": false 17 | }, 18 | "willSave": true, 19 | "willSaveWaitUntil": false 20 | } 21 | } 22 | } 23 | } 24 | ] -------------------------------------------------------------------------------- /tests/formatting/messages/initialized.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "initialized", 4 | "params": {} 5 | } -------------------------------------------------------------------------------- /tests/formatting/messages/initialized.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "method": "textDocument/publishDiagnostics", 5 | "params": { 6 | "diagnostics": [], 7 | "uri": "testFile:///source/app.d" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /tests/formatting/messages/shutdown.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "shutdown", 4 | "method": "shutdown", 5 | "params": {} 6 | } -------------------------------------------------------------------------------- /tests/formatting/messages/shutdown.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "shutdown", 4 | "jsonrpc": "2.0", 5 | "result": null 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_formatting.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/formatting", 4 | "method": "textDocument/formatting", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "options": { 10 | "tabSize": 4, 11 | "insertSpaces": true 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_formatting.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/formatting", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "newText": "\n\n", 8 | "range": { 9 | "end": { 10 | "character": 17, 11 | "line": 0 12 | }, 13 | "start": { 14 | "character": 17, 15 | "line": 0 16 | } 17 | } 18 | }, 19 | { 20 | "newText": "\n", 21 | "range": { 22 | "end": { 23 | "character": 28, 24 | "line": 0 25 | }, 26 | "start": { 27 | "character": 28, 28 | "line": 0 29 | } 30 | } 31 | }, 32 | { 33 | "newText": "\n ", 34 | "range": { 35 | "end": { 36 | "character": 29, 37 | "line": 0 38 | }, 39 | "start": { 40 | "character": 29, 41 | "line": 0 42 | } 43 | } 44 | }, 45 | { 46 | "newText": "\n", 47 | "range": { 48 | "end": { 49 | "character": 1, 50 | "line": 1 51 | }, 52 | "start": { 53 | "character": 1, 54 | "line": 1 55 | } 56 | } 57 | } 58 | ] 59 | } 60 | ] -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_onTypeFormatting.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/onTypeFormatting", 4 | "method": "textDocument/onTypeFormatting", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 0, 11 | "character": 81 12 | }, 13 | "ch": ";", 14 | "options": { 15 | "tabSize": 4, 16 | "insertSpaces": true 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_onTypeFormatting.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/onTypeFormatting", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "newText": "\n\n", 8 | "range": { 9 | "end": { 10 | "character": 17, 11 | "line": 0 12 | }, 13 | "start": { 14 | "character": 17, 15 | "line": 0 16 | } 17 | } 18 | }, 19 | { 20 | "newText": "\n", 21 | "range": { 22 | "end": { 23 | "character": 28, 24 | "line": 0 25 | }, 26 | "start": { 27 | "character": 28, 28 | "line": 0 29 | } 30 | } 31 | }, 32 | { 33 | "newText": "\n ", 34 | "range": { 35 | "end": { 36 | "character": 29, 37 | "line": 0 38 | }, 39 | "start": { 40 | "character": 29, 41 | "line": 0 42 | } 43 | } 44 | } 45 | ] 46 | } 47 | ] -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_rangeFormatting.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/rangeFormatting", 4 | "method": "textDocument/rangeFormatting", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "range": { 10 | "start": { 11 | "line": 0, 12 | "character": 24 13 | }, 14 | "end": { 15 | "line": 0, 16 | "character": 32 17 | } 18 | }, 19 | "options": { 20 | "tabSize": 4, 21 | "insertSpaces": true 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/formatting/messages/textDocument_rangeFormatting.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/rangeFormatting", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "newText": "\n", 8 | "range": { 9 | "end": { 10 | "character": 28, 11 | "line": 0 12 | }, 13 | "start": { 14 | "character": 28, 15 | "line": 0 16 | } 17 | } 18 | }, 19 | { 20 | "newText": "\n ", 21 | "range": { 22 | "end": { 23 | "character": 29, 24 | "line": 0 25 | }, 26 | "start": { 27 | "character": 29, 28 | "line": 0 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /tests/formatting/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio;void main(){writeln("Edit source/app.d to start your project."); 2 | } -------------------------------------------------------------------------------- /tests/hello/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello" 3 | } -------------------------------------------------------------------------------- /tests/hello/messages/_order.txt: -------------------------------------------------------------------------------- 1 | initialize 2 | initialized 3 | shutdown 4 | -------------------------------------------------------------------------------- /tests/hello/messages/initialize.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "initialize", 4 | "method": "initialize", 5 | "params": { 6 | "processId": null, 7 | "rootUri": "testFile:///", 8 | "capabilities": {} 9 | } 10 | } -------------------------------------------------------------------------------- /tests/hello/messages/initialize.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "initialize", 4 | "jsonrpc": "2.0", 5 | "result": { 6 | "capabilities": { 7 | "textDocumentSync": { 8 | "change": 2, 9 | "openClose": true, 10 | "save": { 11 | "includeText": false 12 | }, 13 | "willSave": true, 14 | "willSaveWaitUntil": false 15 | } 16 | } 17 | } 18 | } 19 | ] -------------------------------------------------------------------------------- /tests/hello/messages/initialized.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "initialized", 4 | "params": {} 5 | } -------------------------------------------------------------------------------- /tests/hello/messages/initialized.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "method": "textDocument/publishDiagnostics", 5 | "params": { 6 | "diagnostics": [], 7 | "uri": "testFile:///source/app.d" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /tests/hello/messages/shutdown.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "shutdown", 4 | "method": "shutdown", 5 | "params": {} 6 | } -------------------------------------------------------------------------------- /tests/hello/messages/shutdown.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "shutdown", 4 | "jsonrpc": "2.0", 5 | "result": null 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/hello/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | writeln("Edit source/app.d to start your project."); 6 | } 7 | -------------------------------------------------------------------------------- /tests/main.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | import dls.util.communicator : Communicator; 22 | import std.json : JSONValue; 23 | 24 | private immutable orderFileName = "_order"; 25 | 26 | private enum MessageFileType : string 27 | { 28 | order = ".txt", 29 | input = ".in.json", 30 | output = ".out.json", 31 | reference = ".ref.json" 32 | } 33 | 34 | private struct Message 35 | { 36 | private string name; 37 | private char[] content; 38 | } 39 | 40 | private class TestCommunicator : Communicator 41 | { 42 | string[] testedDirectories; 43 | private Message[][string] _directoriesMessages; 44 | private string _directory; 45 | private string _lastDirectory; 46 | private string _lastReadMessageName; 47 | private string _currentOutput; 48 | private JSONValue[] _outputMessages; 49 | 50 | this() 51 | { 52 | import std.algorithm : filter; 53 | import std.file : SpanMode, dirEntries, getcwd, isDir; 54 | import std.path : buildPath; 55 | 56 | foreach (directory; dirEntries(buildPath(getcwd(), "tests"), SpanMode.shallow).filter!( 57 | entry => isDir(entry.name) && hasMessages(entry.name))) 58 | { 59 | _directoriesMessages[directory] = []; 60 | 61 | if (_directory.length == 0) 62 | { 63 | _directory = directory; 64 | } 65 | } 66 | 67 | fillDirectoryMessages(); 68 | } 69 | 70 | bool hasData() 71 | { 72 | return _directoriesMessages.keys.length > 0; 73 | } 74 | 75 | bool hasPendingData() 76 | { 77 | return false; 78 | } 79 | 80 | char[] read(const size_t size) 81 | { 82 | auto currentMessage = _directoriesMessages[_directory][0]; 83 | auto result = currentMessage.content[0 .. size]; 84 | 85 | if (currentMessage.content.length > size) 86 | { 87 | _directoriesMessages[_directory][0].content = currentMessage.content[size .. $]; 88 | } 89 | else 90 | { 91 | _lastDirectory = _directory; 92 | _lastReadMessageName = currentMessage.name; 93 | _outputMessages = []; 94 | 95 | if (_directoriesMessages[_directory].length > 1) 96 | { 97 | _directoriesMessages[_directory] = _directoriesMessages[_directory][1 .. $]; 98 | } 99 | else 100 | { 101 | testedDirectories ~= _directory; 102 | _directoriesMessages.remove(_directory); 103 | 104 | if (_directoriesMessages.keys.length > 0) 105 | { 106 | _directory = _directoriesMessages.keys[0]; 107 | fillDirectoryMessages(); 108 | } 109 | } 110 | } 111 | 112 | return result; 113 | } 114 | 115 | void write(const char[] buffer) 116 | { 117 | _currentOutput ~= buffer; 118 | } 119 | 120 | void flush() 121 | { 122 | import std.algorithm : findSkip; 123 | import std.file : write; 124 | import std.json : JSONOptions, parseJSON; 125 | 126 | _currentOutput.findSkip("\r\n\r\n"); 127 | _outputMessages ~= parseJSON(_currentOutput); 128 | const outputPath = getMessagePath(_lastDirectory, _lastReadMessageName, 129 | MessageFileType.output); 130 | write(outputPath, JSONValue(_outputMessages) 131 | .toPrettyString(JSONOptions.doNotEscapeSlashes)); 132 | _currentOutput = ""; 133 | } 134 | 135 | private void fillDirectoryMessages() 136 | { 137 | import std.file : readText; 138 | import std.format : format; 139 | 140 | foreach (line; getOrderedMessageNames(_directory)) 141 | { 142 | const inputPath = getMessagePath(_directory, line, MessageFileType.input); 143 | auto input = readText!(char[])(inputPath).expandTestUris(_directory); 144 | auto message = Message(line); 145 | message.content ~= format("Content-Length: %s\r\n\r\n%s", input.length, input); 146 | _directoriesMessages[_directory] ~= message; 147 | } 148 | } 149 | } 150 | 151 | int main() 152 | { 153 | import dls.server : Server; 154 | import dls.util.communicator : communicator; 155 | 156 | auto testCommunicator = new TestCommunicator(); 157 | communicator = testCommunicator; 158 | Server.loop(); 159 | return checkResults(testCommunicator.testedDirectories); 160 | } 161 | 162 | private int checkResults(const string[] directories) 163 | { 164 | import std.algorithm : reduce; 165 | import std.array : array; 166 | import std.format : format; 167 | import std.json : JSONOptions; 168 | import std.range : repeat; 169 | import std.stdio : stderr; 170 | 171 | static void writeHeader(const string header) 172 | { 173 | auto headerLine = repeat('=', header.length); 174 | stderr.writeln(headerLine); 175 | stderr.writeln(header); 176 | stderr.writeln(headerLine); 177 | } 178 | 179 | size_t testCount; 180 | size_t passCount; 181 | 182 | foreach (directory; directories) 183 | { 184 | writeHeader(format("Test directory %s", directory)); 185 | 186 | auto orderedMessageNames = getOrderedMessageNames(directory).array; 187 | const maxNameLength = reduce!((a, b) => a.length > b.length ? a : b)("", 188 | orderedMessageNames).length; 189 | 190 | foreach (name; orderedMessageNames) 191 | { 192 | auto output = getJSON(directory, name, MessageFileType.output); 193 | auto reference = getJSON(directory, name, MessageFileType.reference); 194 | stderr.writef(" * Message %s%s: ", name, repeat(' ', maxNameLength - name.length)); 195 | ++testCount; 196 | 197 | if (output != reference) 198 | { 199 | stderr.writeln("FAIL \u2718"); 200 | writeDiff(reference.toPrettyString(JSONOptions.doNotEscapeSlashes), 201 | output.toPrettyString(JSONOptions.doNotEscapeSlashes)); 202 | } 203 | else 204 | { 205 | stderr.writeln("PASS \u2714"); 206 | ++passCount; 207 | } 208 | } 209 | } 210 | 211 | writeHeader(format("Passed message tests: %s/%s", passCount, testCount)); 212 | return passCount == testCount ? 0 : 1; 213 | } 214 | 215 | private bool hasMessages(const string directory) 216 | { 217 | import std.file : exists; 218 | 219 | return exists(getMessagePath(directory, orderFileName, MessageFileType.order)); 220 | } 221 | 222 | private auto getOrderedMessageNames(const string directory) 223 | { 224 | import std.algorithm : map; 225 | import std.stdio : File; 226 | import std.string : strip; 227 | 228 | return File(getMessagePath(directory, orderFileName, MessageFileType.order), "r") 229 | .byLineCopy.map!strip; 230 | } 231 | 232 | private JSONValue getJSON(const string directory, const string name, const MessageFileType type) 233 | { 234 | import std.algorithm : sort; 235 | import std.array : array; 236 | import std.json : JSONValue, parseJSON; 237 | import std.file : exists, readText; 238 | 239 | const path = getMessagePath(directory, name, type); 240 | auto rawJSON = parseJSON(exists(path) ? readText(path).expandTestUris(directory) : "[]"); 241 | return JSONValue(rawJSON.array.sort!((a, b) => a.toString() < b.toString()).array); 242 | } 243 | 244 | private string getMessagePath(const string directory, const string name, const MessageFileType type) 245 | { 246 | import std.path : buildPath; 247 | 248 | return buildPath(directory, "messages", name ~ type); 249 | } 250 | 251 | private inout(char[]) expandTestUris(inout(char[]) text, const string directory) 252 | { 253 | import dls.util.uri : Uri; 254 | import std.array : replace; 255 | 256 | return text.replace("testFile://", Uri.fromPath(directory).toString()); 257 | } 258 | 259 | private void writeDiff(const string reference, const string output) 260 | { 261 | import std.ascii : newline; 262 | import std.conv : text; 263 | import std.file : remove, tempDir, write; 264 | import std.path : buildPath; 265 | import std.process : Config, execute; 266 | import std.stdio : stderr; 267 | import std.uuid : randomUUID; 268 | 269 | const mainPath = buildPath(tempDir, randomUUID().toString()); 270 | const refPath = mainPath ~ MessageFileType.reference; 271 | const outPath = mainPath ~ MessageFileType.output; 272 | 273 | write(refPath, reference); 274 | write(outPath, output); 275 | 276 | scope (exit) 277 | { 278 | remove(refPath); 279 | remove(outPath); 280 | } 281 | 282 | version (Windows) 283 | { 284 | const args = ["fc.exe", refPath, outPath]; 285 | } 286 | else version (Posix) 287 | { 288 | static immutable maxSize = text(size_t.max); 289 | const args = ["diff", "-U", maxSize, refPath, outPath]; 290 | } 291 | else 292 | { 293 | string[] args; 294 | } 295 | 296 | stderr.write(args.length > 0 ? execute(args, null, Config.suppressConsole) 297 | .output : "No diff output available on this platform" ~ newline); 298 | } 299 | -------------------------------------------------------------------------------- /tests/references/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "references" 3 | } -------------------------------------------------------------------------------- /tests/references/messages/_order.txt: -------------------------------------------------------------------------------- 1 | initialize 2 | initialized 3 | textDocument_references_1 4 | textDocument_references_1_noDeclaration 5 | textDocument_references_2 6 | textDocument_references_2_noDeclaration 7 | textDocument_references_3 8 | textDocument_references_3_noDeclaration 9 | textDocument_references_4 10 | textDocument_references_4_noDeclaration 11 | shutdown 12 | -------------------------------------------------------------------------------- /tests/references/messages/initialize.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "initialize", 4 | "method": "initialize", 5 | "params": { 6 | "processId": null, 7 | "rootUri": "testFile:///", 8 | "capabilities": { 9 | "textDocument": { 10 | "references": { 11 | "dynamicRegistration": false 12 | } 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/references/messages/initialize.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "initialize", 4 | "jsonrpc": "2.0", 5 | "result": { 6 | "capabilities": { 7 | "referencesProvider": true, 8 | "textDocumentSync": { 9 | "change": 2, 10 | "openClose": true, 11 | "save": { 12 | "includeText": false 13 | }, 14 | "willSave": true, 15 | "willSaveWaitUntil": false 16 | } 17 | } 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /tests/references/messages/initialized.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "initialized", 4 | "params": {} 5 | } -------------------------------------------------------------------------------- /tests/references/messages/initialized.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "method": "textDocument/publishDiagnostics", 5 | "params": { 6 | "diagnostics": [ 7 | { 8 | "code": "dscanner.style.undocumented_declaration", 9 | "message": "Public declaration 'Foo' is undocumented.", 10 | "range": { 11 | "end": { 12 | "character": 9, 13 | "line": 2 14 | }, 15 | "start": { 16 | "character": 6, 17 | "line": 2 18 | } 19 | }, 20 | "severity": 2, 21 | "source": "D-Scanner" 22 | }, 23 | { 24 | "code": "dscanner.style.undocumented_declaration", 25 | "message": "Public declaration 'member' is undocumented.", 26 | "range": { 27 | "end": { 28 | "character": 14, 29 | "line": 4 30 | }, 31 | "start": { 32 | "character": 8, 33 | "line": 4 34 | } 35 | }, 36 | "severity": 2, 37 | "source": "D-Scanner" 38 | } 39 | ], 40 | "uri": "testFile:///source/app.d" 41 | } 42 | } 43 | ] -------------------------------------------------------------------------------- /tests/references/messages/shutdown.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "shutdown", 4 | "method": "shutdown", 5 | "params": {} 6 | } -------------------------------------------------------------------------------- /tests/references/messages/shutdown.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "shutdown", 4 | "jsonrpc": "2.0", 5 | "result": null 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_1.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 1", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 9, 11 | "character": 20 12 | }, 13 | "context": { 14 | "includeDeclaration": true 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_1.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 1", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 9, 10 | "line": 2 11 | }, 12 | "start": { 13 | "character": 6, 14 | "line": 2 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 22, 23 | "line": 9 24 | }, 25 | "start": { 26 | "character": 19, 27 | "line": 9 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | } 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_1_noDeclaration.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 1 no declaration", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 9, 11 | "character": 20 12 | }, 13 | "context": { 14 | "includeDeclaration": false 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_1_noDeclaration.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 1 no declaration", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 22, 10 | "line": 9 11 | }, 12 | "start": { 13 | "character": 19, 14 | "line": 9 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | } 19 | ] 20 | } 21 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_2.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 2", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 10, 11 | "character": 8 12 | }, 13 | "context": { 14 | "includeDeclaration": true 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_2.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 2", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 11, 10 | "line": 10 11 | }, 12 | "start": { 13 | "character": 4, 14 | "line": 10 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 11, 23 | "line": 12 24 | }, 25 | "start": { 26 | "character": 4, 27 | "line": 12 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | } 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_2_noDeclaration.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 2 no declaration", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 10, 11 | "character": 8 12 | }, 13 | "context": { 14 | "includeDeclaration": false 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_2_noDeclaration.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 2 no declaration", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 11, 10 | "line": 10 11 | }, 12 | "start": { 13 | "character": 4, 14 | "line": 10 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 11, 23 | "line": 12 24 | }, 25 | "start": { 26 | "character": 4, 27 | "line": 12 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | } 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_3.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 3", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 10, 11 | "character": 14 12 | }, 13 | "context": { 14 | "includeDeclaration": true 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_3.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 3", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 12, 10 | "line": 9 11 | }, 12 | "start": { 13 | "character": 9, 14 | "line": 9 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 15, 23 | "line": 10 24 | }, 25 | "start": { 26 | "character": 12, 27 | "line": 10 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | }, 32 | { 33 | "range": { 34 | "end": { 35 | "character": 7, 36 | "line": 11 37 | }, 38 | "start": { 39 | "character": 4, 40 | "line": 11 41 | } 42 | }, 43 | "uri": "testFile:///source/app.d" 44 | }, 45 | { 46 | "range": { 47 | "end": { 48 | "character": 15, 49 | "line": 12 50 | }, 51 | "start": { 52 | "character": 12, 53 | "line": 12 54 | } 55 | }, 56 | "uri": "testFile:///source/app.d" 57 | } 58 | ] 59 | } 60 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_3_noDeclaration.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 3 no declaration", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 10, 11 | "character": 14 12 | }, 13 | "context": { 14 | "includeDeclaration": false 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_3_noDeclaration.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 3 no declaration", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 15, 10 | "line": 10 11 | }, 12 | "start": { 13 | "character": 12, 14 | "line": 10 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 7, 23 | "line": 11 24 | }, 25 | "start": { 26 | "character": 4, 27 | "line": 11 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | }, 32 | { 33 | "range": { 34 | "end": { 35 | "character": 15, 36 | "line": 12 37 | }, 38 | "start": { 39 | "character": 12, 40 | "line": 12 41 | } 42 | }, 43 | "uri": "testFile:///source/app.d" 44 | } 45 | ] 46 | } 47 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_4.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 4", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 12, 11 | "character": 20 12 | }, 13 | "context": { 14 | "includeDeclaration": true 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_4.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 4", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 14, 10 | "line": 4 11 | }, 12 | "start": { 13 | "character": 8, 14 | "line": 4 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 22, 23 | "line": 10 24 | }, 25 | "start": { 26 | "character": 16, 27 | "line": 10 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | }, 32 | { 33 | "range": { 34 | "end": { 35 | "character": 14, 36 | "line": 11 37 | }, 38 | "start": { 39 | "character": 8, 40 | "line": 11 41 | } 42 | }, 43 | "uri": "testFile:///source/app.d" 44 | }, 45 | { 46 | "range": { 47 | "end": { 48 | "character": 22, 49 | "line": 12 50 | }, 51 | "start": { 52 | "character": 16, 53 | "line": 12 54 | } 55 | }, 56 | "uri": "testFile:///source/app.d" 57 | } 58 | ] 59 | } 60 | ] -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_4_noDeclaration.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/references 4 no declaration", 4 | "method": "textDocument/references", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | }, 9 | "position": { 10 | "line": 12, 11 | "character": 20 12 | }, 13 | "context": { 14 | "includeDeclaration": false 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /tests/references/messages/textDocument_references_4_noDeclaration.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/references 4 no declaration", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "range": { 8 | "end": { 9 | "character": 22, 10 | "line": 10 11 | }, 12 | "start": { 13 | "character": 16, 14 | "line": 10 15 | } 16 | }, 17 | "uri": "testFile:///source/app.d" 18 | }, 19 | { 20 | "range": { 21 | "end": { 22 | "character": 14, 23 | "line": 11 24 | }, 25 | "start": { 26 | "character": 8, 27 | "line": 11 28 | } 29 | }, 30 | "uri": "testFile:///source/app.d" 31 | }, 32 | { 33 | "range": { 34 | "end": { 35 | "character": 22, 36 | "line": 12 37 | }, 38 | "start": { 39 | "character": 16, 40 | "line": 12 41 | } 42 | }, 43 | "uri": "testFile:///source/app.d" 44 | } 45 | ] 46 | } 47 | ] -------------------------------------------------------------------------------- /tests/references/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | class Foo 4 | { 5 | int member; 6 | } 7 | 8 | void main() 9 | { 10 | auto foo = new Foo(); 11 | writeln(foo.member); 12 | foo.member += 42; 13 | writeln(foo.member); 14 | } 15 | -------------------------------------------------------------------------------- /tests/symbol/dscanner.ini: -------------------------------------------------------------------------------- 1 | ; Configure which static analysis checks are enabled 2 | [analysis.config.StaticAnalysisConfig] 3 | ; Check variable, class, struct, interface, union, and function names against t 4 | ; he Phobos style guide 5 | style_check="enabled" 6 | ; Check for array literals that cause unnecessary allocation 7 | enum_array_literal_check="enabled" 8 | ; Check for poor exception handling practices 9 | exception_check="enabled" 10 | ; Check for use of the deprecated 'delete' keyword 11 | delete_check="enabled" 12 | ; Check for use of the deprecated floating point operators 13 | float_operator_check="enabled" 14 | ; Check number literals for readability 15 | number_style_check="enabled" 16 | ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable 17 | ; , or inout. 18 | object_const_check="enabled" 19 | ; Checks for .. expressions where the left side is larger than the right. 20 | backwards_range_check="enabled" 21 | ; Checks for if statements whose 'then' block is the same as the 'else' block 22 | if_else_same_check="enabled" 23 | ; Checks for some problems with constructors 24 | constructor_check="enabled" 25 | ; Checks for unused variables and function parameters 26 | unused_variable_check="disabled" 27 | ; Checks for unused labels 28 | unused_label_check="enabled" 29 | ; Checks for duplicate attributes 30 | duplicate_attribute="enabled" 31 | ; Checks that opEquals and toHash are both defined or neither are defined 32 | opequals_tohash_check="enabled" 33 | ; Checks for subtraction from .length properties 34 | length_subtraction_check="enabled" 35 | ; Checks for methods or properties whose names conflict with built-in propertie 36 | ; s 37 | builtin_property_names_check="enabled" 38 | ; Checks for confusing code in inline asm statements 39 | asm_style_check="enabled" 40 | ; Checks for confusing logical operator precedence 41 | logical_precedence_check="enabled" 42 | ; Checks for undocumented public declarations 43 | undocumented_declaration_check="disabled" 44 | ; Checks for poor placement of function attributes 45 | function_attribute_check="enabled" 46 | ; Checks for use of the comma operator 47 | comma_expression_check="enabled" 48 | ; Checks for local imports that are too broad 49 | local_import_check="enabled" 50 | ; Checks for variables that could be declared immutable 51 | could_be_immutable_check="disabled" 52 | ; Checks for redundant expressions in if statements 53 | redundant_if_check="enabled" 54 | ; Checks for redundant parenthesis 55 | redundant_parens_check="enabled" 56 | ; Checks for mismatched argument and parameter names 57 | mismatched_args_check="enabled" 58 | ; Checks for labels with the same name as variables 59 | label_var_same_name_check="enabled" 60 | ; Checks for lines longer than 120 characters 61 | long_line_check="enabled" 62 | ; Checks for assignment to auto-ref function parameters 63 | auto_ref_assignment_check="enabled" 64 | ; Checks for incorrect infinite range definitions 65 | incorrect_infinite_range_check="enabled" 66 | ; Checks for asserts that are always true 67 | useless_assert_check="enabled" 68 | ; Check for uses of the old-style alias syntax 69 | alias_syntax_check="enabled" 70 | ; Checks for else if that should be else static if 71 | static_if_else_check="enabled" 72 | ; Check for unclear lambda syntax 73 | lambda_return_check="enabled" 74 | ; Check for auto function without return statement 75 | auto_function_check="enabled" 76 | ; Check for sortedness of imports 77 | imports_sortedness="disabled" 78 | ; Check for explicitly annotated unittests 79 | explicitly_annotated_unittests="disabled" 80 | ; Check for properly documented public functions (Returns, Params) 81 | properly_documented_public_functions="disabled" 82 | ; Check for useless usage of the final attribute 83 | final_attribute_check="enabled" 84 | ; Check for virtual calls in the class constructors 85 | vcall_in_ctor="enabled" 86 | ; Check for useless user defined initializers 87 | useless_initializer="disabled" 88 | ; Check allman brace style 89 | allman_braces_check="disabled" 90 | ; Check for redundant attributes 91 | redundant_attributes_check="enabled" 92 | ; Check public declarations without a documented unittest 93 | has_public_example="disabled" 94 | ; Check for asserts without an explanatory message 95 | assert_without_msg="disabled" 96 | ; Check indent of if constraints 97 | if_constraints_indent="disabled" 98 | ; Check for @trusted applied to a bigger scope than a single function 99 | trust_too_much="enabled" 100 | ; Check for redundant storage classes on variable declarations 101 | redundant_storage_classes="enabled" 102 | ; ModuleFilters for selectively enabling (+std) and disabling (-std.internal) i 103 | ; ndividual checks 104 | [analysis.config.ModuleFilters] 105 | ; Exclude/Import modules 106 | style_check="" 107 | ; Exclude/Import modules 108 | enum_array_literal_check="" 109 | ; Exclude/Import modules 110 | exception_check="" 111 | ; Exclude/Import modules 112 | delete_check="" 113 | ; Exclude/Import modules 114 | float_operator_check="" 115 | ; Exclude/Import modules 116 | number_style_check="" 117 | ; Exclude/Import modules 118 | object_const_check="" 119 | ; Exclude/Import modules 120 | backwards_range_check="" 121 | ; Exclude/Import modules 122 | if_else_same_check="" 123 | ; Exclude/Import modules 124 | constructor_check="" 125 | ; Exclude/Import modules 126 | unused_variable_check="" 127 | ; Exclude/Import modules 128 | unused_label_check="" 129 | ; Exclude/Import modules 130 | duplicate_attribute="" 131 | ; Exclude/Import modules 132 | opequals_tohash_check="" 133 | ; Exclude/Import modules 134 | length_subtraction_check="" 135 | ; Exclude/Import modules 136 | builtin_property_names_check="" 137 | ; Exclude/Import modules 138 | asm_style_check="" 139 | ; Exclude/Import modules 140 | logical_precedence_check="" 141 | ; Exclude/Import modules 142 | undocumented_declaration_check="" 143 | ; Exclude/Import modules 144 | function_attribute_check="" 145 | ; Exclude/Import modules 146 | comma_expression_check="" 147 | ; Exclude/Import modules 148 | local_import_check="" 149 | ; Exclude/Import modules 150 | could_be_immutable_check="" 151 | ; Exclude/Import modules 152 | redundant_if_check="" 153 | ; Exclude/Import modules 154 | redundant_parens_check="" 155 | ; Exclude/Import modules 156 | mismatched_args_check="" 157 | ; Exclude/Import modules 158 | label_var_same_name_check="" 159 | ; Exclude/Import modules 160 | long_line_check="" 161 | ; Exclude/Import modules 162 | auto_ref_assignment_check="" 163 | ; Exclude/Import modules 164 | incorrect_infinite_range_check="" 165 | ; Exclude/Import modules 166 | useless_assert_check="" 167 | ; Exclude/Import modules 168 | alias_syntax_check="" 169 | ; Exclude/Import modules 170 | static_if_else_check="" 171 | ; Exclude/Import modules 172 | lambda_return_check="" 173 | ; Exclude/Import modules 174 | auto_function_check="" 175 | ; Exclude/Import modules 176 | imports_sortedness="" 177 | ; Exclude/Import modules 178 | explicitly_annotated_unittests="" 179 | ; Exclude/Import modules 180 | properly_documented_public_functions="" 181 | ; Exclude/Import modules 182 | final_attribute_check="" 183 | ; Exclude/Import modules 184 | vcall_in_ctor="" 185 | ; Exclude/Import modules 186 | useless_initializer="" 187 | ; Exclude/Import modules 188 | allman_braces_check="" 189 | ; Exclude/Import modules 190 | redundant_attributes_check="" 191 | ; Exclude/Import modules 192 | has_public_example="" 193 | ; Exclude/Import modules 194 | assert_without_msg="" 195 | ; Exclude/Import modules 196 | if_constraints_indent="" 197 | ; Exclude/Import modules 198 | trust_too_much="" 199 | ; Exclude/Import modules 200 | redundant_storage_classes="" 201 | -------------------------------------------------------------------------------- /tests/symbol/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symbol" 3 | } -------------------------------------------------------------------------------- /tests/symbol/messages/_order.txt: -------------------------------------------------------------------------------- 1 | initialize-symbolInformation 2 | initialized 3 | textDocument_documentSymbol-symbolInformation 4 | workspace_symbol 5 | shutdown 6 | initialize-documentSymbol 7 | initialized 8 | textDocument_documentSymbol-documentSymbol 9 | shutdown 10 | -------------------------------------------------------------------------------- /tests/symbol/messages/initialize-documentSymbol.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "initialize", 4 | "method": "initialize", 5 | "params": { 6 | "processId": null, 7 | "rootUri": "testFile:///", 8 | "capabilities": { 9 | "workspace": { 10 | "symbol": { 11 | "dynamicRegistration": false 12 | } 13 | }, 14 | "textDocument": { 15 | "documentSymbol": { 16 | "dynamicRegistration": false, 17 | "hierarchicalDocumentSymbolSupport": true 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/symbol/messages/initialize-documentSymbol.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "initialize", 4 | "jsonrpc": "2.0", 5 | "result": { 6 | "capabilities": { 7 | "documentSymbolProvider": true, 8 | "textDocumentSync": { 9 | "change": 2, 10 | "openClose": true, 11 | "save": { 12 | "includeText": false 13 | }, 14 | "willSave": true, 15 | "willSaveWaitUntil": false 16 | }, 17 | "workspaceSymbolProvider": true 18 | } 19 | } 20 | } 21 | ] -------------------------------------------------------------------------------- /tests/symbol/messages/initialize-symbolInformation.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "initialize", 4 | "method": "initialize", 5 | "params": { 6 | "processId": null, 7 | "rootUri": "testFile:///", 8 | "capabilities": { 9 | "workspace": { 10 | "symbol": { 11 | "dynamicRegistration": false 12 | } 13 | }, 14 | "textDocument": { 15 | "documentSymbol": { 16 | "dynamicRegistration": false 17 | } 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/symbol/messages/initialize-symbolInformation.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "initialize", 4 | "jsonrpc": "2.0", 5 | "result": { 6 | "capabilities": { 7 | "documentSymbolProvider": true, 8 | "textDocumentSync": { 9 | "change": 2, 10 | "openClose": true, 11 | "save": { 12 | "includeText": false 13 | }, 14 | "willSave": true, 15 | "willSaveWaitUntil": false 16 | }, 17 | "workspaceSymbolProvider": true 18 | } 19 | } 20 | } 21 | ] -------------------------------------------------------------------------------- /tests/symbol/messages/initialized.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "initialized", 4 | "params": {} 5 | } -------------------------------------------------------------------------------- /tests/symbol/messages/initialized.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "method": "textDocument/publishDiagnostics", 5 | "params": { 6 | "diagnostics": [], 7 | "uri": "testFile:///source/lib.d" 8 | } 9 | }, 10 | { 11 | "jsonrpc": "2.0", 12 | "method": "textDocument/publishDiagnostics", 13 | "params": { 14 | "diagnostics": [], 15 | "uri": "testFile:///source/app.d" 16 | } 17 | } 18 | ] -------------------------------------------------------------------------------- /tests/symbol/messages/shutdown.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "shutdown", 4 | "method": "shutdown", 5 | "params": {} 6 | } -------------------------------------------------------------------------------- /tests/symbol/messages/shutdown.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "shutdown", 4 | "jsonrpc": "2.0", 5 | "result": null 6 | } 7 | ] -------------------------------------------------------------------------------- /tests/symbol/messages/textDocument_documentSymbol-documentSymbol.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/documentSymbol", 4 | "method": "textDocument/documentSymbol", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/symbol/messages/textDocument_documentSymbol-symbolInformation.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "textDocument/documentSymbol", 4 | "method": "textDocument/documentSymbol", 5 | "params": { 6 | "textDocument": { 7 | "uri": "testFile:///source/app.d" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/symbol/messages/textDocument_documentSymbol-symbolInformation.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "textDocument/documentSymbol", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "containerName": "", 8 | "kind": 12, 9 | "location": { 10 | "range": { 11 | "end": { 12 | "character": 9, 13 | "line": 2 14 | }, 15 | "start": { 16 | "character": 5, 17 | "line": 2 18 | } 19 | }, 20 | "uri": "testFile:///source/app.d" 21 | }, 22 | "name": "main" 23 | }, 24 | { 25 | "containerName": "", 26 | "kind": 5, 27 | "location": { 28 | "range": { 29 | "end": { 30 | "character": 11, 31 | "line": 8 32 | }, 33 | "start": { 34 | "character": 6, 35 | "line": 8 36 | } 37 | }, 38 | "uri": "testFile:///source/app.d" 39 | }, 40 | "name": "Class" 41 | }, 42 | { 43 | "containerName": "Class", 44 | "kind": 13, 45 | "location": { 46 | "range": { 47 | "end": { 48 | "character": 14, 49 | "line": 10 50 | }, 51 | "start": { 52 | "character": 8, 53 | "line": 10 54 | } 55 | }, 56 | "uri": "testFile:///source/app.d" 57 | }, 58 | "name": "number" 59 | }, 60 | { 61 | "containerName": "Class", 62 | "kind": 11, 63 | "location": { 64 | "range": { 65 | "end": { 66 | "character": 9, 67 | "line": 12 68 | }, 69 | "start": { 70 | "character": 4, 71 | "line": 12 72 | } 73 | }, 74 | "uri": "testFile:///source/app.d" 75 | }, 76 | "name": "" 77 | }, 78 | { 79 | "containerName": "", 80 | "kind": 13, 81 | "location": { 82 | "range": { 83 | "end": { 84 | "character": 21, 85 | "line": 14 86 | }, 87 | "start": { 88 | "character": 12, 89 | "line": 14 90 | } 91 | }, 92 | "uri": "testFile:///source/app.d" 93 | }, 94 | "name": "anonymous" 95 | }, 96 | { 97 | "containerName": "", 98 | "kind": 13, 99 | "location": { 100 | "range": { 101 | "end": { 102 | "character": 21, 103 | "line": 15 104 | }, 105 | "start": { 106 | "character": 15, 107 | "line": 15 108 | } 109 | }, 110 | "uri": "testFile:///source/app.d" 111 | }, 112 | "name": "symbol" 113 | }, 114 | { 115 | "containerName": "", 116 | "kind": 23, 117 | "location": { 118 | "range": { 119 | "end": { 120 | "character": 13, 121 | "line": 19 122 | }, 123 | "start": { 124 | "character": 7, 125 | "line": 19 126 | } 127 | }, 128 | "uri": "testFile:///source/app.d" 129 | }, 130 | "name": "Struct" 131 | }, 132 | { 133 | "containerName": "Struct", 134 | "kind": 13, 135 | "location": { 136 | "range": { 137 | "end": { 138 | "character": 15, 139 | "line": 21 140 | }, 141 | "start": { 142 | "character": 11, 143 | "line": 21 144 | } 145 | }, 146 | "uri": "testFile:///source/app.d" 147 | }, 148 | "name": "name" 149 | }, 150 | { 151 | "containerName": "", 152 | "kind": 10, 153 | "location": { 154 | "range": { 155 | "end": { 156 | "character": 9, 157 | "line": 24 158 | }, 159 | "start": { 160 | "character": 5, 161 | "line": 24 162 | } 163 | }, 164 | "uri": "testFile:///source/app.d" 165 | }, 166 | "name": "Enum" 167 | }, 168 | { 169 | "containerName": "Enum", 170 | "kind": 22, 171 | "location": { 172 | "range": { 173 | "end": { 174 | "character": 7, 175 | "line": 26 176 | }, 177 | "start": { 178 | "character": 4, 179 | "line": 26 180 | } 181 | }, 182 | "uri": "testFile:///source/app.d" 183 | }, 184 | "name": "foo" 185 | }, 186 | { 187 | "containerName": "Enum", 188 | "kind": 22, 189 | "location": { 190 | "range": { 191 | "end": { 192 | "character": 7, 193 | "line": 27 194 | }, 195 | "start": { 196 | "character": 4, 197 | "line": 27 198 | } 199 | }, 200 | "uri": "testFile:///source/app.d" 201 | }, 202 | "name": "bar" 203 | }, 204 | { 205 | "containerName": "Enum", 206 | "kind": 22, 207 | "location": { 208 | "range": { 209 | "end": { 210 | "character": 7, 211 | "line": 28 212 | }, 213 | "start": { 214 | "character": 4, 215 | "line": 28 216 | } 217 | }, 218 | "uri": "testFile:///source/app.d" 219 | }, 220 | "name": "baz" 221 | } 222 | ] 223 | } 224 | ] -------------------------------------------------------------------------------- /tests/symbol/messages/workspace_symbol.in.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "workspace/symbol", 4 | "method": "workspace/symbol", 5 | "params": { 6 | "query": "" 7 | } 8 | } -------------------------------------------------------------------------------- /tests/symbol/messages/workspace_symbol.ref.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "workspace/symbol", 4 | "jsonrpc": "2.0", 5 | "result": [ 6 | { 7 | "containerName": "", 8 | "kind": 12, 9 | "location": { 10 | "range": { 11 | "end": { 12 | "character": 9, 13 | "line": 2 14 | }, 15 | "start": { 16 | "character": 5, 17 | "line": 2 18 | } 19 | }, 20 | "uri": "testFile:///source/app.d" 21 | }, 22 | "name": "main" 23 | }, 24 | { 25 | "containerName": "", 26 | "kind": 5, 27 | "location": { 28 | "range": { 29 | "end": { 30 | "character": 11, 31 | "line": 8 32 | }, 33 | "start": { 34 | "character": 6, 35 | "line": 8 36 | } 37 | }, 38 | "uri": "testFile:///source/app.d" 39 | }, 40 | "name": "Class" 41 | }, 42 | { 43 | "containerName": "Class", 44 | "kind": 13, 45 | "location": { 46 | "range": { 47 | "end": { 48 | "character": 14, 49 | "line": 10 50 | }, 51 | "start": { 52 | "character": 8, 53 | "line": 10 54 | } 55 | }, 56 | "uri": "testFile:///source/app.d" 57 | }, 58 | "name": "number" 59 | }, 60 | { 61 | "containerName": "Class", 62 | "kind": 13, 63 | "location": { 64 | "range": { 65 | "end": { 66 | "character": 21, 67 | "line": 14 68 | }, 69 | "start": { 70 | "character": 12, 71 | "line": 14 72 | } 73 | }, 74 | "uri": "testFile:///source/app.d" 75 | }, 76 | "name": "anonymous" 77 | }, 78 | { 79 | "containerName": "Class", 80 | "kind": 13, 81 | "location": { 82 | "range": { 83 | "end": { 84 | "character": 21, 85 | "line": 15 86 | }, 87 | "start": { 88 | "character": 15, 89 | "line": 15 90 | } 91 | }, 92 | "uri": "testFile:///source/app.d" 93 | }, 94 | "name": "symbol" 95 | }, 96 | { 97 | "containerName": "", 98 | "kind": 23, 99 | "location": { 100 | "range": { 101 | "end": { 102 | "character": 13, 103 | "line": 19 104 | }, 105 | "start": { 106 | "character": 7, 107 | "line": 19 108 | } 109 | }, 110 | "uri": "testFile:///source/app.d" 111 | }, 112 | "name": "Struct" 113 | }, 114 | { 115 | "containerName": "Struct", 116 | "kind": 12, 117 | "location": { 118 | "range": { 119 | "end": { 120 | "character": 13, 121 | "line": 19 122 | }, 123 | "start": { 124 | "character": 7, 125 | "line": 19 126 | } 127 | }, 128 | "uri": "testFile:///source/app.d" 129 | }, 130 | "name": "this" 131 | }, 132 | { 133 | "containerName": "Struct", 134 | "kind": 13, 135 | "location": { 136 | "range": { 137 | "end": { 138 | "character": 15, 139 | "line": 21 140 | }, 141 | "start": { 142 | "character": 11, 143 | "line": 21 144 | } 145 | }, 146 | "uri": "testFile:///source/app.d" 147 | }, 148 | "name": "name" 149 | }, 150 | { 151 | "containerName": "", 152 | "kind": 10, 153 | "location": { 154 | "range": { 155 | "end": { 156 | "character": 9, 157 | "line": 24 158 | }, 159 | "start": { 160 | "character": 5, 161 | "line": 24 162 | } 163 | }, 164 | "uri": "testFile:///source/app.d" 165 | }, 166 | "name": "Enum" 167 | }, 168 | { 169 | "containerName": "Enum", 170 | "kind": 22, 171 | "location": { 172 | "range": { 173 | "end": { 174 | "character": 7, 175 | "line": 26 176 | }, 177 | "start": { 178 | "character": 4, 179 | "line": 26 180 | } 181 | }, 182 | "uri": "testFile:///source/app.d" 183 | }, 184 | "name": "foo" 185 | }, 186 | { 187 | "containerName": "Enum", 188 | "kind": 22, 189 | "location": { 190 | "range": { 191 | "end": { 192 | "character": 7, 193 | "line": 27 194 | }, 195 | "start": { 196 | "character": 4, 197 | "line": 27 198 | } 199 | }, 200 | "uri": "testFile:///source/app.d" 201 | }, 202 | "name": "bar" 203 | }, 204 | { 205 | "containerName": "Enum", 206 | "kind": 22, 207 | "location": { 208 | "range": { 209 | "end": { 210 | "character": 7, 211 | "line": 28 212 | }, 213 | "start": { 214 | "character": 4, 215 | "line": 28 216 | } 217 | }, 218 | "uri": "testFile:///source/app.d" 219 | }, 220 | "name": "baz" 221 | }, 222 | { 223 | "containerName": "", 224 | "kind": 12, 225 | "location": { 226 | "range": { 227 | "end": { 228 | "character": 17, 229 | "line": 0 230 | }, 231 | "start": { 232 | "character": 5, 233 | "line": 0 234 | } 235 | }, 236 | "uri": "testFile:///source/lib.d" 237 | }, 238 | "name": "someFunction" 239 | }, 240 | { 241 | "containerName": "", 242 | "kind": 11, 243 | "location": { 244 | "range": { 245 | "end": { 246 | "character": 23, 247 | "line": 5 248 | }, 249 | "start": { 250 | "character": 10, 251 | "line": 5 252 | } 253 | }, 254 | "uri": "testFile:///source/lib.d" 255 | }, 256 | "name": "SomeInterface" 257 | } 258 | ] 259 | } 260 | ] -------------------------------------------------------------------------------- /tests/symbol/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main() 4 | { 5 | auto var = 42; 6 | writeln("Edit source/app.d to start your project."); 7 | } 8 | 9 | class Class 10 | { 11 | int number; 12 | 13 | union 14 | { 15 | int anonymous; 16 | string symbol; 17 | } 18 | } 19 | 20 | struct Struct 21 | { 22 | string name; 23 | } 24 | 25 | enum Enum 26 | { 27 | foo, 28 | bar, 29 | baz 30 | } 31 | -------------------------------------------------------------------------------- /tests/symbol/source/lib.d: -------------------------------------------------------------------------------- 1 | void someFunction() 2 | { 3 | //... 4 | } 5 | 6 | interface SomeInterface 7 | { 8 | //... 9 | } 10 | -------------------------------------------------------------------------------- /util/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "util", 3 | "authors": [ 4 | "Laurent Tréguier" 5 | ], 6 | "description": "DLS utilities", 7 | "copyright": "Copyright © 2018, Laurent Tréguier", 8 | "license": "GPL-3.0 or later", 9 | "dependencies": { 10 | "dls:protocol": "*" 11 | } 12 | } -------------------------------------------------------------------------------- /util/source/dls/util/communicator.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.communicator; 22 | 23 | import std.stdio : File; 24 | 25 | // Socket can't be used with a shared aliasing. 26 | // However, the communicator's methods are all called either from the main 27 | // thread, or from inside a synchronized block, so __gshared is ok. 28 | private __gshared Communicator _communicator; 29 | private __gshared File _stdin; 30 | private __gshared File _stdout; 31 | 32 | shared static this() 33 | { 34 | import std.stdio : stdin, stdout; 35 | 36 | _stdin = stdin; 37 | _stdout = stdout; 38 | 39 | version (Windows) 40 | { 41 | stdin = File("NUL", "rb"); 42 | stdout = File("NUL", "wb"); 43 | } 44 | else version (Posix) 45 | { 46 | stdin = File("/dev/null", "rb"); 47 | stdout = File("/dev/null", "wb"); 48 | } 49 | } 50 | 51 | shared static ~this() 52 | { 53 | if (_communicator !is null) 54 | { 55 | destroy(communicator); 56 | } 57 | } 58 | 59 | @property Communicator communicator() 60 | { 61 | return _communicator; 62 | } 63 | 64 | @property void communicator(Communicator c) 65 | { 66 | assert(_communicator is null); 67 | _communicator = c; 68 | } 69 | 70 | interface Communicator 71 | { 72 | bool hasData(); 73 | bool hasPendingData(); 74 | char[] read(const size_t size); 75 | void write(const char[] data); 76 | void flush(); 77 | } 78 | 79 | class StdioCommunicator : Communicator 80 | { 81 | import std.parallelism : Task, TaskPool; 82 | 83 | private bool _checkPending; 84 | private TaskPool _pool; 85 | private Task!(readChar)* _background; 86 | 87 | static char readChar() 88 | { 89 | static char[1] buffer; 90 | auto result = _stdin.rawRead(buffer); 91 | 92 | if (result.length > 0) 93 | { 94 | return result[0]; 95 | } 96 | 97 | throw new Exception("No input data"); 98 | } 99 | 100 | this(bool checkPendingData) 101 | { 102 | _checkPending = checkPendingData; 103 | 104 | if (checkPendingData) 105 | { 106 | _pool = new TaskPool(1); 107 | _pool.isDaemon = true; 108 | startBackground(); 109 | } 110 | } 111 | 112 | ~this() 113 | { 114 | if (_checkPending) 115 | { 116 | _pool.stop(); 117 | } 118 | } 119 | 120 | bool hasData() 121 | { 122 | return _stdin.isOpen && !_stdin.eof; 123 | } 124 | 125 | bool hasPendingData() 126 | { 127 | try 128 | { 129 | return _checkPending && _background.done; 130 | } 131 | catch (Exception e) 132 | { 133 | return false; 134 | } 135 | } 136 | 137 | char[] read(const size_t size) 138 | { 139 | if (size == 0) 140 | { 141 | return []; 142 | } 143 | 144 | static char[] buffer; 145 | buffer.length = size; 146 | 147 | if (!_checkPending) 148 | { 149 | return _stdin.rawRead(buffer); 150 | } 151 | 152 | scope(exit) 153 | { 154 | startBackground(); 155 | } 156 | 157 | try 158 | { 159 | buffer[0] = _background.yieldForce(); 160 | } 161 | catch (Exception e) 162 | { 163 | return hasData() ? _stdin.rawRead(buffer) : []; 164 | } 165 | 166 | if (size > 1) 167 | { 168 | buffer = buffer[0 .. _stdin.rawRead(buffer[1 .. $]).length + 1]; 169 | } 170 | 171 | return buffer; 172 | } 173 | 174 | void write(const char[] data) 175 | { 176 | _stdout.rawWrite(data); 177 | } 178 | 179 | void flush() 180 | { 181 | _stdout.flush(); 182 | } 183 | 184 | private void startBackground() 185 | { 186 | import std.parallelism : task; 187 | 188 | if (_checkPending && hasData()) 189 | { 190 | _background = task!readChar; 191 | _pool.put(_background); 192 | } 193 | } 194 | } 195 | 196 | class SocketCommunicator : Communicator 197 | { 198 | import std.socket : Socket; 199 | 200 | private Socket _socket; 201 | 202 | this(ushort port) 203 | { 204 | import std.socket : AddressInfo, InternetAddress, SocketOption, 205 | SocketOptionLevel, TcpSocket; 206 | 207 | _socket = new TcpSocket(new InternetAddress("localhost", port)); 208 | _socket.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1); 209 | } 210 | 211 | bool hasData() 212 | { 213 | synchronized (_socket) 214 | { 215 | return _socket.isAlive; 216 | } 217 | } 218 | 219 | bool hasPendingData() 220 | { 221 | import std.socket : SocketFlags; 222 | 223 | static char[1] buffer; 224 | ptrdiff_t result; 225 | 226 | synchronized (_socket) 227 | { 228 | _socket.blocking = false; 229 | result = _socket.receive(buffer, SocketFlags.PEEK); 230 | _socket.blocking = true; 231 | } 232 | 233 | return result != Socket.ERROR && result > 0; 234 | } 235 | 236 | char[] read(const size_t size) 237 | { 238 | static char[] buffer; 239 | buffer.length = size; 240 | ptrdiff_t totalBytesReceived; 241 | ptrdiff_t bytesReceived; 242 | 243 | do 244 | { 245 | synchronized (_socket) 246 | { 247 | bytesReceived = _socket.receive(buffer); 248 | } 249 | 250 | if (bytesReceived != Socket.ERROR) 251 | { 252 | totalBytesReceived += bytesReceived; 253 | } 254 | else if (bytesReceived == 0) 255 | { 256 | buffer.length = totalBytesReceived; 257 | break; 258 | } 259 | } 260 | while (bytesReceived == Socket.ERROR || totalBytesReceived < size); 261 | 262 | return buffer; 263 | } 264 | 265 | void write(const char[] data) 266 | { 267 | ptrdiff_t totalBytesSent; 268 | ptrdiff_t bytesSent; 269 | 270 | do 271 | { 272 | synchronized (_socket) 273 | { 274 | bytesSent = _socket.send(data[totalBytesSent .. $]); 275 | } 276 | 277 | if (bytesSent != Socket.ERROR) 278 | { 279 | totalBytesSent += bytesSent; 280 | } 281 | } 282 | while (bytesSent == Socket.ERROR || totalBytesSent < data.length); 283 | } 284 | 285 | void flush() 286 | { 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /util/source/dls/util/disposable_fiber.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.disposable_fiber; 22 | 23 | import core.thread : Fiber; 24 | 25 | class FiberDisposedException : Exception 26 | { 27 | this() 28 | { 29 | super("Fiber disposed"); 30 | } 31 | } 32 | 33 | class DisposableFiber : Fiber 34 | { 35 | static bool safeMode; 36 | private bool _disposed; 37 | 38 | static DisposableFiber getThis() 39 | { 40 | const thisFiber = Fiber.getThis(); 41 | assert(typeid(thisFiber) == typeid(DisposableFiber)); 42 | return cast(DisposableFiber) thisFiber; 43 | } 44 | 45 | static void yield() 46 | { 47 | if (safeMode) 48 | { 49 | return; 50 | } 51 | 52 | Fiber.yield(); 53 | 54 | if (getThis()._disposed) 55 | { 56 | throw new FiberDisposedException(); 57 | } 58 | } 59 | 60 | this(void delegate() dg) 61 | { 62 | size_t pageSize; 63 | 64 | version (Windows) 65 | { 66 | import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO; 67 | 68 | SYSTEM_INFO info; 69 | GetSystemInfo(&info); 70 | pageSize = cast(size_t) info.dwPageSize; 71 | } 72 | else version (Posix) 73 | { 74 | import core.sys.posix.unistd : _SC_PAGESIZE, sysconf; 75 | 76 | pageSize = cast(size_t) sysconf(_SC_PAGESIZE); 77 | } 78 | else 79 | { 80 | pageSize = 4096; 81 | } 82 | 83 | super(dg, pageSize * 32); 84 | } 85 | 86 | void dispose() 87 | { 88 | _disposed = true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /util/source/dls/util/document.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.document; 22 | 23 | class Document 24 | { 25 | import dls.util.uri : Uri; 26 | import dls.protocol.definitions : DocumentUri, Position, Range, 27 | TextDocumentIdentifier, TextDocumentItem, VersionedTextDocumentIdentifier; 28 | import dls.protocol.interfaces : TextDocumentContentChangeEvent; 29 | import std.json : JSONValue; 30 | 31 | private static Document[string] _documents; 32 | private DocumentUri _uri; 33 | private wstring[] _lines; 34 | private JSONValue _version; 35 | 36 | @property static auto uris() 37 | { 38 | import std.algorithm : map; 39 | 40 | return _documents.byValue.map!(doc => new Uri(doc._uri)); 41 | } 42 | 43 | static Document get(const Uri uri) 44 | { 45 | import std.file : readText; 46 | 47 | return uri.path in _documents ? _documents[uri.path] : new Document(uri, readText(uri.path)); 48 | } 49 | 50 | static bool open(const TextDocumentItem textDocument) 51 | { 52 | auto uri = new Uri(textDocument.uri); 53 | 54 | if (uri.path !in _documents) 55 | { 56 | _documents[uri.path] = new Document(uri, textDocument.text); 57 | _documents[uri.path]._version = textDocument.version_; 58 | return true; 59 | } 60 | else 61 | { 62 | return false; 63 | } 64 | } 65 | 66 | static bool close(const TextDocumentIdentifier textDocument) 67 | { 68 | auto uri = new Uri(textDocument.uri); 69 | 70 | if (uri.path in _documents) 71 | { 72 | _documents.remove(uri.path); 73 | return true; 74 | } 75 | else 76 | { 77 | return false; 78 | } 79 | } 80 | 81 | static bool change(const VersionedTextDocumentIdentifier textDocument, 82 | TextDocumentContentChangeEvent[] events) 83 | { 84 | auto uri = new Uri(textDocument.uri); 85 | 86 | if (uri.path in _documents) 87 | { 88 | _documents[uri.path].change(events); 89 | _documents[uri.path]._version = textDocument.version_; 90 | return true; 91 | } 92 | else 93 | { 94 | return false; 95 | } 96 | } 97 | 98 | @property const(wstring[]) lines() const 99 | { 100 | return _lines; 101 | } 102 | 103 | @property JSONValue version_() const 104 | { 105 | return _version; 106 | } 107 | 108 | private this(const Uri uri, const string text) 109 | { 110 | _uri = uri; 111 | _lines = getText(text); 112 | } 113 | 114 | override string toString() const 115 | { 116 | import std.range : join; 117 | import std.utf : toUTF8; 118 | 119 | return _lines.join().toUTF8(); 120 | } 121 | 122 | void validatePosition(const Position position) const 123 | { 124 | import dls.protocol.errors : InvalidParamsException; 125 | import std.format : format; 126 | 127 | if (position.line >= _lines.length || position.character > _lines[position.line].length) 128 | { 129 | throw new InvalidParamsException(format!"invalid position: %s %s,%s"(_uri, 130 | position.line, position.character)); 131 | } 132 | } 133 | 134 | size_t byteAtPosition(const Position position) const 135 | { 136 | import std.algorithm : min; 137 | import std.utf : codeLength; 138 | 139 | size_t result; 140 | 141 | foreach (i, ref line; _lines) 142 | { 143 | if (i < position.line) 144 | { 145 | result += codeLength!char(line); 146 | } 147 | else 148 | { 149 | result += codeLength!char(line[0 .. min(position.character, $)]); 150 | break; 151 | } 152 | } 153 | 154 | return result; 155 | } 156 | 157 | Position positionAtByte(size_t bytePosition) const 158 | { 159 | import std.algorithm : min; 160 | import std.utf : codeLength, toUTF8; 161 | 162 | size_t i; 163 | size_t bytes; 164 | 165 | while (bytes <= bytePosition && i < _lines.length) 166 | { 167 | bytes += codeLength!char(_lines[i]); 168 | ++i; 169 | } 170 | 171 | immutable lineNumber = minusOne(i); 172 | immutable line = _lines[lineNumber]; 173 | bytes -= codeLength!char(line); 174 | immutable columnByte = min(bytePosition - bytes, line.length); 175 | immutable columnNumber = codeLength!wchar(line.toUTF8()[0 .. columnByte]); 176 | return new Position(lineNumber, columnNumber); 177 | } 178 | 179 | Range wordRangeAtPosition(const Position position) const 180 | { 181 | import std.algorithm : min; 182 | 183 | immutable line = _lines[min(position.line, $ - 1)]; 184 | immutable middleIndex = min(position.character, line.length); 185 | size_t startIndex = middleIndex; 186 | size_t endIndex = middleIndex; 187 | 188 | static bool isIdentifierChar(wchar c) 189 | { 190 | import std.ascii : isPunctuation, isWhite; 191 | 192 | return !isWhite(c) && (!isPunctuation(c) || c == '_'); 193 | } 194 | 195 | while (startIndex > 0 && isIdentifierChar(line[minusOne(startIndex)])) 196 | { 197 | --startIndex; 198 | } 199 | 200 | while (endIndex < line.length && isIdentifierChar(line[endIndex])) 201 | { 202 | ++endIndex; 203 | } 204 | 205 | return new Range(new Position(position.line, startIndex), 206 | new Position(position.line, endIndex)); 207 | } 208 | 209 | Range wordRangeAtLineAndByte(size_t lineNumber, size_t bytePosition) const 210 | { 211 | import std.algorithm : min; 212 | import std.utf : codeLength, toUTF8; 213 | 214 | return wordRangeAtPosition(new Position(lineNumber, 215 | codeLength!wchar(_lines[lineNumber].toUTF8()[0 .. min(bytePosition, $)]))); 216 | } 217 | 218 | Range wordRangeAtByte(size_t bytePosition) const 219 | { 220 | return wordRangeAtPosition(positionAtByte(bytePosition)); 221 | } 222 | 223 | private void change(const TextDocumentContentChangeEvent[] events) 224 | { 225 | foreach (event; events) 226 | { 227 | if (event.range.isNull) 228 | { 229 | _lines = getText(event.text); 230 | } 231 | else 232 | { 233 | with (event.range) 234 | { 235 | auto linesBefore = _lines[0 .. start.line]; 236 | auto linesAfter = _lines[end.line + 1 .. $]; 237 | 238 | auto lineStart = _lines[start.line][0 .. start.character]; 239 | auto lineEnd = _lines[end.line][end.character .. $]; 240 | 241 | auto newLines = getText(event.text); 242 | 243 | if (newLines.length) 244 | { 245 | newLines[0] = lineStart ~ newLines[0]; 246 | newLines[$ - 1] = newLines[$ - 1] ~ lineEnd; 247 | } 248 | else 249 | { 250 | newLines = [lineStart ~ lineEnd]; 251 | } 252 | 253 | _lines = linesBefore ~ newLines ~ linesAfter; 254 | } 255 | } 256 | } 257 | } 258 | 259 | private wstring[] getText(const string text) const 260 | { 261 | import std.algorithm : endsWith; 262 | import std.array : replaceFirst; 263 | import std.encoding : getBOM; 264 | import std.string : splitLines; 265 | import std.typecons : Yes; 266 | import std.utf : toUTF16; 267 | 268 | auto lines = text.replaceFirst(cast(string) getBOM(cast(ubyte[]) text) 269 | .sequence, "").toUTF16().splitLines(Yes.keepTerminator); 270 | 271 | if (!lines.length || lines[$ - 1].endsWith('\r', '\n')) 272 | { 273 | lines ~= ""; 274 | } 275 | 276 | return lines; 277 | } 278 | } 279 | 280 | size_t minusOne(size_t i) 281 | { 282 | return i > 0 ? i - 1 : 0; 283 | } 284 | -------------------------------------------------------------------------------- /util/source/dls/util/uri.d: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright (C) 2018 Laurent Tréguier 3 | * 4 | *This file is part of DLS. 5 | * 6 | *DLS is free software: you can redistribute it and/or modify 7 | *it under the terms of the GNU General Public License as published by 8 | *the Free Software Foundation, either version 3 of the License, or 9 | *(at your option) any later version. 10 | * 11 | *DLS is distributed in the hope that it will be useful, 12 | *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | *GNU General Public License for more details. 15 | * 16 | *You should have received a copy of the GNU General Public License 17 | *along with DLS. If not, see . 18 | * 19 | */ 20 | 21 | module dls.util.uri; 22 | 23 | class Uri 24 | { 25 | import dls.protocol.definitions : DocumentUri; 26 | import std.regex : regex; 27 | 28 | private static enum _reg = regex(`([\w-]+):(?://([\w.@]+(?::\d+)?))?([^\?#]+)(?:\?([\w=&]+))?(?:#([\w-]+))?`); 29 | private string _uri; 30 | private string _scheme; 31 | private string _authority; 32 | private string _path; 33 | private string _query; 34 | private string _fragment; 35 | 36 | @property string path() const 37 | { 38 | return _path; 39 | } 40 | 41 | this(DocumentUri uri) 42 | { 43 | import std.regex : matchAll; 44 | import std.uri : decodeComponent; 45 | 46 | _uri = decodeComponent(uri); 47 | auto matches = matchAll(_uri, _reg); 48 | 49 | //dfmt off 50 | _scheme = matches.front[1]; 51 | _authority = matches.front[2]; 52 | _path = matches.front[3].normalized; 53 | _query = matches.front[4]; 54 | _fragment = matches.front[5]; 55 | //dfmt on 56 | } 57 | 58 | private this(string scheme, string authority, string path, string query, string fragment) 59 | { 60 | _uri = scheme ~ ':'; 61 | 62 | if (authority !is null) 63 | { 64 | _uri ~= "//" ~ authority; 65 | } 66 | 67 | _uri ~= path; 68 | 69 | if (query !is null) 70 | { 71 | _uri ~= '?' ~ query; 72 | } 73 | 74 | if (fragment !is null) 75 | { 76 | _uri ~= '#' ~ fragment; 77 | } 78 | 79 | //dfmt off 80 | _scheme = scheme; 81 | _authority = authority; 82 | _path = path.normalized; 83 | _query = query; 84 | _fragment = fragment; 85 | //dfmt on 86 | } 87 | 88 | override string toString() const 89 | { 90 | return _uri; 91 | } 92 | 93 | static Uri fromPath(string path) 94 | { 95 | import std.algorithm : startsWith; 96 | import std.format : format; 97 | import std.string : tr; 98 | import std.uri : encode; 99 | 100 | immutable uriPath = path.tr(`\`, `/`); 101 | return new Uri("file", "", (uriPath.startsWith('/') ? "" : "/") ~ uriPath, null, null); 102 | } 103 | 104 | alias toString this; 105 | } 106 | 107 | string normalized(const string path) 108 | { 109 | import std.array : array; 110 | import std.path : asNormalizedPath; 111 | 112 | string res; 113 | 114 | version (Windows) 115 | { 116 | import std.algorithm : startsWith; 117 | import std.path : driveName, stripDrive; 118 | import std.uni : asUpperCase; 119 | import std.utf : toUTF8; 120 | 121 | if (path.startsWith('/') || path.startsWith('\\')) 122 | { 123 | return path[1 .. $].normalized; 124 | } 125 | 126 | res = driveName(path).asUpperCase().toUTF8() ~ stripDrive(path); 127 | } 128 | else 129 | { 130 | res = path; 131 | } 132 | 133 | return asNormalizedPath(res).array; 134 | } 135 | 136 | int filenameCmp(const Uri a, const Uri b) 137 | { 138 | import std.path : filenameCmp; 139 | 140 | return filenameCmp(a.path, b.path); 141 | } 142 | 143 | bool sameFile(const Uri a, const Uri b) 144 | { 145 | return filenameCmp(a, b) == 0; 146 | } 147 | --------------------------------------------------------------------------------