├── .github ├── FUNDING.yml └── workflows │ ├── nightly.yml │ ├── releases.yml │ └── unittest.yml ├── .gitignore ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── ci ├── build.bat ├── build.sh └── validate_version.d ├── dcd ├── README.md ├── dub.sdl ├── dub.selections.json └── source │ └── served │ └── dcd │ └── client.d ├── dub.json ├── dub.selections.json ├── editor-emacs.md ├── editor-helix.md ├── editor-lite-xl.md ├── editor-nova.md ├── editor-st.md ├── editor-vim.md ├── editor-zed.md ├── http ├── dub.sdl ├── dub.selections.json └── source │ └── served │ └── http.d ├── index-utils ├── .gitignore ├── dub.sdl ├── dub.selections.json └── source │ └── app.d ├── lsp ├── dub.sdl ├── dub.selections.json └── source │ └── served │ └── lsp │ ├── filereader.d │ ├── jsonrpc.d │ └── textdocumentmanager.d ├── null_server ├── .gitignore ├── dub.sdl └── source │ ├── app.d │ └── null_server │ └── extension.d ├── null_server_test ├── .gitignore ├── dub.sdl └── source │ └── app.d ├── protocol ├── dub.sdl ├── dub.selections.json └── source │ └── served │ └── lsp │ ├── jsonops.d │ ├── protocol.d │ └── uri.d ├── reggaefile.d ├── release.bat ├── release.sh ├── serverbase ├── dub.sdl ├── dub.selections.json └── source │ └── served │ ├── serverbase.d │ └── utils │ ├── async.d │ ├── events.d │ ├── fibermanager.d │ ├── memory.d │ └── serverconfig.d ├── source ├── app.d ├── crash_handler.d ├── external │ └── ldc │ │ └── config.d └── served │ ├── backend │ └── lazy_workspaced.d │ ├── commands │ ├── calltips.d │ ├── code_actions.d │ ├── code_lens.d │ ├── color.d │ ├── complete.d │ ├── dcd_update.d │ ├── definition.d │ ├── dub.d │ ├── file_search.d │ ├── folding.d │ ├── format.d │ ├── highlight.d │ ├── index.d │ ├── references.d │ ├── rename.d │ ├── symbol_search.d │ └── test_provider.d │ ├── extension.d │ ├── info.d │ ├── io │ ├── git_build.d │ ├── http_wrap.d │ └── nothrow_fs.d │ ├── linters │ ├── dfmt.d │ ├── diagnosticmanager.d │ ├── dscanner.d │ └── dub.d │ ├── lsp │ └── protoext.d │ ├── types.d │ ├── utils │ ├── ddoc.d │ ├── diet.d │ ├── progress.d │ ├── stdlib_detect.d │ ├── trace.d │ └── translate.d │ └── workers │ ├── profilegc.d │ └── rename_listener.d ├── sponsors └── weka.png ├── test.bat ├── test.sh ├── test ├── .gitignore ├── data │ ├── dcd │ │ └── download_dcd.d │ ├── list_definition │ │ ├── _basic.d │ │ ├── _verbose.d │ │ └── static_ctors.d │ └── snippet_info │ │ ├── _basic.d │ │ ├── dot_exception.d │ │ ├── identifiers.d │ │ └── traits.d ├── runtests.sh ├── tc_as_a_exe │ ├── dub.sdl │ ├── source │ │ ├── app.d │ │ └── tests │ │ │ ├── _basic.d │ │ │ └── package.d │ └── template │ │ ├── README │ │ ├── dub.sdl │ │ └── source │ │ └── app.d ├── tc_dub │ ├── dub.sdl │ └── source │ │ └── app.d ├── tc_dub_dependencies │ ├── dub.sdl │ ├── project │ │ ├── dub.sdl │ │ └── source │ │ │ └── app.d │ └── source │ │ └── app.d ├── tc_dub_empty │ ├── dub.sdl │ ├── empty1 │ │ └── dub.sdl │ ├── empty2 │ │ └── dub.json │ ├── empty3 │ │ └── dub.sdl │ ├── empty_windows │ │ └── dub.json │ ├── invalid │ │ └── dub.json │ ├── missing_dep │ │ ├── dub.sdl │ │ └── source │ │ │ └── app.d │ ├── source │ │ └── app.d │ ├── sourcelib │ │ ├── dub.json │ │ └── source │ │ │ └── lib.d │ └── valid │ │ ├── dub.sdl │ │ └── source │ │ └── app.d ├── tc_fsworkspace │ ├── dub.sdl │ └── source │ │ └── app.d ├── tc_implement_interface │ ├── .needs_dcd │ ├── dub.sdl │ ├── source │ │ └── app.d │ └── tests │ │ ├── basic.d │ │ ├── basic.d.expected │ │ ├── implicit_abstract.d │ │ ├── implicit_abstract.d.expected │ │ ├── nested_preimplemented.d │ │ ├── nested_preimplemented.d.expected │ │ ├── nested_types.d │ │ ├── nested_types.d.expected │ │ ├── poly.d │ │ ├── poly.d.expected │ │ ├── preimplemented.d │ │ ├── preimplemented.d.expected │ │ ├── properties.d │ │ └── properties.d.expected ├── tc_integrated_dfmt │ ├── dub.sdl │ └── source │ │ └── app.d └── tc_integrated_dscanner │ ├── dub.sdl │ └── source │ └── app.d ├── views ├── ddocs.txt ├── de.txt ├── en.txt ├── fr.txt ├── ja.txt └── ru.txt └── workspace-d ├── .editorconfig ├── .env.fish ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── README.md ├── dml_gen ├── dub.json └── source │ ├── app.d │ └── lookupgen.d ├── dub.sdl ├── dub.selections.json ├── source └── workspaced │ ├── api.d │ ├── backend.d │ ├── com │ ├── ccdb.d │ ├── dcd.d │ ├── dcd_version.d │ ├── dcdext.d │ ├── dfmt.d │ ├── dlangui.d │ ├── dmd.d │ ├── dscanner.d │ ├── dub.d │ ├── fsworkspace.d │ ├── importer.d │ ├── index.d │ ├── moduleman.d │ ├── references.d │ └── snippets │ │ ├── _package_tests.d │ │ ├── control_flow.d │ │ ├── dependencies.d │ │ ├── external_builtin.d │ │ ├── generator.d │ │ ├── package.d │ │ ├── plain.d │ │ └── smart.d │ ├── completion │ └── dml.d │ ├── coms.d │ ├── dparseext.d │ ├── dub │ ├── diagnostics.d │ └── lintgenerator.d │ ├── future.d │ ├── helpers.d │ ├── index_format.d │ └── visitors │ ├── attributes.d │ ├── classifier.d │ └── methodfinder.d └── views └── dml-completion.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [WebFreak001] 2 | patreon: WebFreak 3 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Nightly 2 | on: 3 | schedule: 4 | - cron: '0 2 * * *' 5 | workflow_dispatch: 6 | 7 | jobs: 8 | nightly: 9 | name: Deploy nightly 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | # ubuntu / linux must run on kinda old linux for glibc compatibility! 14 | os: [ubuntu-20.04, windows-latest, macos-latest] 15 | arch: [x86_64] 16 | include: 17 | # use for 32 bit build 18 | - os: windows-latest 19 | arch: x86 20 | - os: macos-latest 21 | arch: arm64-apple-macos 22 | arch_short: arm64 23 | build: release 24 | cross: "1" 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - id: vars 30 | shell: bash 31 | run: | 32 | if [ -z "${{matrix.arch_short}}" ]; then 33 | echo "arch_short=${{matrix.arch}}" >> $GITHUB_OUTPUT 34 | else 35 | echo "arch_short=${{matrix.arch_short}}" >> $GITHUB_OUTPUT 36 | fi 37 | if [ -z "${{matrix.build}}" ]; then 38 | echo "build=release-debug" >> $GITHUB_OUTPUT 39 | else 40 | echo "build=${{matrix.build}}" >> $GITHUB_OUTPUT 41 | fi 42 | 43 | - name: Install D compiler 44 | uses: dlang-community/setup-dlang@v1 45 | with: 46 | compiler: ldc-latest 47 | 48 | - name: Run tests 49 | run: dub test 50 | 51 | # Linux release 52 | - name: Build Linux release 53 | run: ./ci/build.sh 54 | if: matrix.os == 'ubuntu-20.04' 55 | env: 56 | ARCH: ${{ matrix.arch }} 57 | ARCH_SHORT: ${{ steps.vars.outputs.arch_short }} 58 | BUILD: ${{ steps.vars.outputs.build }} 59 | 60 | - name: Deploy Linux release 61 | if: matrix.os == 'ubuntu-20.04' 62 | uses: WebFreak001/deploy-nightly@master 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | with: 66 | upload_url: https://uploads.github.com/repos/Pure-D/serve-d/releases/20717582/assets{?name,label} 67 | release_id: 20717582 68 | asset_path: ./serve-d.tar.xz 69 | asset_name: serve-d_linux-nightly-${{ steps.vars.outputs.arch_short }}-$$.tar.xz 70 | asset_content_type: application/x-gtar 71 | 72 | - name: Deploy Linux release (tar.gz) 73 | if: matrix.os == 'ubuntu-20.04' 74 | uses: WebFreak001/deploy-nightly@master 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | with: 78 | upload_url: https://uploads.github.com/repos/Pure-D/serve-d/releases/20717582/assets{?name,label} 79 | release_id: 20717582 80 | asset_path: ./serve-d.tar.gz 81 | asset_name: serve-d_linux-nightly-${{ steps.vars.outputs.arch_short }}-$$.tar.gz 82 | asset_content_type: application/gzip 83 | 84 | # OSX release 85 | - name: Build OSX release 86 | run: ./ci/build.sh 87 | if: matrix.os == 'macos-latest' 88 | env: 89 | MACOSX_DEPLOYMENT_TARGET: '10.12' 90 | ARCH: ${{ matrix.arch }} 91 | ARCH_SHORT: ${{ steps.vars.outputs.arch_short }} 92 | BUILD: ${{ steps.vars.outputs.build }} 93 | CROSS: ${{ matrix.cross }} 94 | 95 | - name: Deploy OSX release 96 | if: matrix.os == 'macos-latest' 97 | uses: WebFreak001/deploy-nightly@master 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | upload_url: https://uploads.github.com/repos/Pure-D/serve-d/releases/20717582/assets{?name,label} 102 | release_id: 20717582 103 | asset_path: ./serve-d.tar.xz 104 | asset_name: serve-d_osx-nightly-${{ steps.vars.outputs.arch_short }}-$$.tar.xz 105 | asset_content_type: application/x-gtar 106 | 107 | - name: Deploy OSX release (tar.gz) 108 | if: matrix.os == 'macos-latest' 109 | uses: WebFreak001/deploy-nightly@master 110 | env: 111 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 112 | with: 113 | upload_url: https://uploads.github.com/repos/Pure-D/serve-d/releases/20717582/assets{?name,label} 114 | release_id: 20717582 115 | asset_path: ./serve-d.tar.gz 116 | asset_name: serve-d_osx-nightly-${{ steps.vars.outputs.arch_short }}-$$.tar.gz 117 | asset_content_type: application/gzip 118 | 119 | # Windows release 120 | - name: Build Windows release 121 | run: .\ci\build.bat 122 | if: matrix.os == 'windows-latest' 123 | env: 124 | BUILD: ${{ steps.vars.outputs.build }} 125 | ARCH: ${{ matrix.arch }} 126 | ARCH_SHORT: ${{ steps.vars.outputs.arch_short }} 127 | CROSS: ${{ matrix.cross }} 128 | 129 | - name: Deploy Windows release 130 | if: matrix.os == 'windows-latest' 131 | uses: WebFreak001/deploy-nightly@master 132 | env: 133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 134 | with: 135 | upload_url: https://uploads.github.com/repos/Pure-D/serve-d/releases/20717582/assets{?name,label} 136 | release_id: 20717582 137 | asset_path: ./serve-d.zip 138 | asset_name: serve-d_windows-nightly-${{ steps.vars.outputs.arch_short }}-$$.zip 139 | asset_content_type: application/zip 140 | 141 | - name: cache dependency binaries 142 | uses: WebFreak001/dub-upgrade@v0.1.0 143 | with: 144 | store: true 145 | -------------------------------------------------------------------------------- /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: Upload prebuild binaries 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | nightly: 8 | name: Deploy releases 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | # ubuntu / linux must run on kinda old linux for glibc compatibility! 13 | os: [ubuntu-20.04, windows-latest, macos-latest] 14 | arch: [x86_64] 15 | include: 16 | # use for 32 bit build 17 | - os: windows-latest 18 | arch: x86 19 | - os: macos-latest 20 | arch: arm64-apple-macos 21 | arch_short: arm64 22 | cross: "1" 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - id: vars 28 | shell: bash 29 | run: | 30 | if [ -z "${{matrix.arch_short}}" ]; then 31 | echo "arch_short=${{matrix.arch}}" >> $GITHUB_OUTPUT 32 | else 33 | echo "arch_short=${{matrix.arch_short}}" >> $GITHUB_OUTPUT 34 | fi 35 | 36 | - name: Install D compiler 37 | uses: dlang-community/setup-dlang@v1 38 | with: 39 | compiler: ldc-latest 40 | 41 | - name: Run tests 42 | run: dub test 43 | 44 | # Linux release 45 | - name: Build Linux release 46 | run: ./ci/build.sh && rdmd ci/validate_version.d 47 | if: matrix.os == 'ubuntu-20.04' 48 | env: 49 | ARCH: ${{ matrix.arch }} 50 | BUILD: release 51 | CROSS: ${{ matrix.cross }} 52 | 53 | - name: Deploy Linux release 54 | if: matrix.os == 'ubuntu-20.04' 55 | uses: WebFreak001/upload-asset@master 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | OS: linux-${{ steps.vars.outputs.arch_short }} 59 | with: 60 | file: ./serve-d.tar.xz 61 | mime: application/x-gtar 62 | name: serve-d_${TAG}-${OS}.tar.xz 63 | 64 | - name: Deploy Linux release (tar.gz) 65 | if: matrix.os == 'ubuntu-20.04' 66 | uses: WebFreak001/upload-asset@master 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | OS: linux-${{ steps.vars.outputs.arch_short }} 70 | with: 71 | file: ./serve-d.tar.gz 72 | mime: application/gzip 73 | name: serve-d_${TAG}-${OS}.tar.gz 74 | 75 | # OSX release 76 | - name: Build OSX release 77 | run: ./ci/build.sh && rdmd ci/validate_version.d 78 | if: matrix.os == 'macos-latest' 79 | env: 80 | MACOSX_DEPLOYMENT_TARGET: '10.12' 81 | ARCH: ${{ matrix.arch }} 82 | BUILD: release 83 | CROSS: ${{ matrix.cross }} 84 | 85 | - name: Deploy OSX release 86 | if: matrix.os == 'macos-latest' 87 | uses: WebFreak001/upload-asset@master 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | OS: osx-${{ steps.vars.outputs.arch_short }} 91 | with: 92 | file: ./serve-d.tar.xz 93 | mime: application/x-gtar 94 | name: serve-d_${TAG}-${OS}.tar.xz 95 | 96 | - name: Deploy OSX release (tar.gz) 97 | if: matrix.os == 'macos-latest' 98 | uses: WebFreak001/upload-asset@master 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | OS: osx-${{ steps.vars.outputs.arch_short }} 102 | with: 103 | file: ./serve-d.tar.gz 104 | mime: application/gzip 105 | name: serve-d_${TAG}-${OS}.tar.gz 106 | 107 | # Windows release 108 | - name: Build Windows release 109 | run: (.\ci\build.bat) -and (rdmd ci\validate_version.d) 110 | if: matrix.os == 'windows-latest' 111 | env: 112 | BUILD: release 113 | ARCH: ${{ matrix.arch }} 114 | CROSS: ${{ matrix.cross }} 115 | 116 | - name: Deploy Windows release 117 | if: matrix.os == 'windows-latest' 118 | uses: WebFreak001/upload-asset@master 119 | env: 120 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 121 | OS: windows-${{ steps.vars.outputs.arch_short }} 122 | with: 123 | file: ./serve-d.zip 124 | mime: application/zip 125 | name: serve-d_${TAG}-${OS}.zip 126 | 127 | - name: cache dependency binaries 128 | uses: WebFreak001/dub-upgrade@v0.1.0 129 | with: 130 | store: true 131 | -------------------------------------------------------------------------------- /.github/workflows/unittest.yml: -------------------------------------------------------------------------------- 1 | name: Run Unittests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | jobs: 9 | dubtest: 10 | name: Dub Tests 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | # ubuntu / linux must run on kinda old linux for glibc compatibility! 15 | os: [ubuntu-20.04, windows-latest, macos-latest] 16 | dc: [dmd-latest, ldc-latest] 17 | exclude: 18 | - os: windows-latest 19 | dc: dmd-latest 20 | - os: macos-latest 21 | dc: dmd-latest 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Install D compiler 27 | uses: dlang-community/setup-dlang@v1 28 | timeout-minutes: 5 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | 32 | - name: Run workspace-d tests 33 | run: dub test :workspace-d 34 | timeout-minutes: 30 35 | env: 36 | MACOSX_DEPLOYMENT_TARGET: '10.12' 37 | 38 | - name: serialization tests 39 | run: dub test :protocol 40 | timeout-minutes: 10 41 | env: 42 | # shouldn't break other OSes 43 | MACOSX_DEPLOYMENT_TARGET: '10.12' 44 | 45 | - name: DCD communication library tests 46 | run: dub test :dcd 47 | timeout-minutes: 5 48 | env: 49 | # shouldn't break other OSes 50 | MACOSX_DEPLOYMENT_TARGET: '10.12' 51 | 52 | - name: LSP tests 53 | run: dub test :lsp 54 | timeout-minutes: 10 55 | env: 56 | # shouldn't break other OSes 57 | MACOSX_DEPLOYMENT_TARGET: '10.12' 58 | 59 | - name: serverbase tests 60 | run: dub test :serverbase 61 | timeout-minutes: 10 62 | env: 63 | MACOSX_DEPLOYMENT_TARGET: '10.12' 64 | 65 | - name: build minimal server 66 | run: dub build --root=null_server 67 | timeout-minutes: 10 68 | env: 69 | MACOSX_DEPLOYMENT_TARGET: '10.12' 70 | 71 | - name: test minimal server 72 | run: dub run --root=null_server_test 73 | timeout-minutes: 10 74 | env: 75 | MACOSX_DEPLOYMENT_TARGET: '10.12' 76 | 77 | - name: Run tests 78 | run: dub test 79 | timeout-minutes: 30 80 | env: 81 | MACOSX_DEPLOYMENT_TARGET: '10.12' 82 | 83 | - name: Run standalone tests 84 | run: ./runtests.sh 85 | working-directory: ./test 86 | timeout-minutes: 30 87 | env: 88 | MACOSX_DEPLOYMENT_TARGET: '10.12' 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.a 6 | *.obj 7 | *-test-* 8 | /serve-d 9 | *.dll 10 | *.lib 11 | *.exe 12 | *.tar.xz 13 | *.zip 14 | /version.txt 15 | /bin/ 16 | *.lst 17 | /build/ 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "gdb", 6 | "request": "launch", 7 | "name": "Launch Tests", 8 | "target": "./serve-d-test-unittest", 9 | "cwd": "${workspaceRoot}", 10 | "showDevDebugOutput": true, 11 | "printCalls": true 12 | }, 13 | { 14 | "type": "gdb", 15 | "request": "attach", 16 | "name": "Attach to code-d instance", 17 | "executable": "./serve-d", 18 | "target": ":2345", 19 | "remote": true, 20 | "cwd": "${workspaceRoot}" 21 | }, 22 | { 23 | "name": "Debug test :lsp", 24 | "type": "code-d", 25 | "debugger": "code-lldb", 26 | "request": "launch", 27 | "program": "${workspaceFolder}/lsp/serve-d-lsp-test-library" 28 | }, 29 | { 30 | "name": "debug test :protocol", 31 | "type": "code-d", 32 | "debugger": "code-lldb", 33 | "request": "launch", 34 | "program": "${workspaceFolder}/protocol/serve-d-protocol-test-library" 35 | }, 36 | { 37 | "name": "debug test mir-ion", 38 | "type": "code-d", 39 | "debugger": "code-lldb", 40 | "request": "launch", 41 | "program": "${workspaceFolder}/mir-ion/mir-ion-test-unittest" 42 | }, 43 | { 44 | "type": "gdb", 45 | "request": "attach", 46 | "name": "Attach to gdbserver null_server", 47 | "executable": "./null_server/null_server", 48 | "target": ":2345", 49 | "remote": true, 50 | "stopAtConnect": true, 51 | "stopAtEntry": true, 52 | "cwd": "${workspaceRoot}", 53 | "autorun": ["source /home/webfreak/.vscode-oss/extensions/webfreak.code-d-0.23.2/dlang-debug/gdb_dlang.py"] 54 | }, 55 | // { 56 | // "name": "Attach to serve-d process", 57 | // "type": "lldb", 58 | // "request": "attach", 59 | // "program": "${workspaceFolder}/serve-d", 60 | // "pid": "${command:pickMyProcess}", 61 | // "initCommands": ["command script import \"/home/webfreak/dev/dlang-debug/lldb_dlang.py\""] 62 | // }, 63 | { 64 | "name": "Attach to serve-d process", 65 | "type": "cppdbg", 66 | "request": "attach", 67 | "program": "${workspaceFolder}/serve-d", 68 | "processId": "${command:pickProcess}", 69 | "MIMode": "gdb", 70 | "setupCommands": [ 71 | { 72 | "description": "Enable pretty-printing for gdb", 73 | "text": "-enable-pretty-printing", 74 | "ignoreFailures": true 75 | }, 76 | { 77 | "description": "Load D GDB type extensions", 78 | "ignoreFailures": false, 79 | "text": "-interpreter-exec console \"source /home/webfreak/dev/dlang-debug/gdb_dlang.py\"" 80 | } 81 | ] 82 | }, 83 | // { 84 | // "type": "gdb", 85 | // "request": "attach", 86 | // "name": "Attach to serve-d process", 87 | // "target": "108200", 88 | // "executable": "${workspaceFolder}/serve-d", 89 | // "cwd": "${workspaceRoot}", 90 | // "valuesFormatting": "prettyPrinters", 91 | // "autorun": ["source /home/webfreak/dev/dlang-debug/gdb_dlang.py", "handle SIGUSR1 noprint", "handle SIGUSR2 noprint"] 92 | // } 93 | ] 94 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [gh@webfreak.org](mailto:gh@webfreak.org). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 Jan Jurzitza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ci/build.bat: -------------------------------------------------------------------------------- 1 | @echo on 2 | 3 | dub build --compiler=ldc2 --build=%BUILD% --arch=%ARCH% 4 | 5 | serve-d.exe --version 6 | 7 | 7z a serve-d.zip serve-d.exe libcurl.dll libeay32.dll ssleay32.dll 8 | -------------------------------------------------------------------------------- /ci/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | set -x 3 | 4 | dub build --compiler=ldc2 --build=$BUILD --arch=$ARCH 5 | strip serve-d 6 | if [ "$CROSS" = 1 ]; then 7 | ls serve-d 8 | else 9 | ./serve-d --version 10 | fi 11 | 12 | tar cfJ serve-d.tar.xz serve-d 13 | tar czf serve-d.tar.gz serve-d 14 | -------------------------------------------------------------------------------- /ci/validate_version.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rdmd 2 | 3 | import std.algorithm; 4 | import std.exception; 5 | import std.file; 6 | import std.json; 7 | import std.process; 8 | import std.stdio; 9 | import std.string; 10 | 11 | void main() 12 | { 13 | version (Windows) 14 | string served = ".\\serve-d.exe"; 15 | else 16 | string served = "./serve-d"; 17 | 18 | if (environment.get("CROSS").length) 19 | { 20 | writeln("Not checking if serve-d version is up-to-date because it's cross-compiled!"); 21 | return; 22 | } 23 | 24 | auto res = execute([served, "--version"]); 25 | enforce(res.status == 0, "serve-d --version didn't return status 0"); 26 | 27 | string output = res.output.strip; 28 | enforce(output.startsWith("serve-d standalone v"), "serve-d --version didn't begin with `serve-d standalone v`"); 29 | output = output["serve-d standalone v".length .. $]; 30 | 31 | auto space = output.indexOfAny(" \t\r\n"); 32 | if (space != -1) 33 | output = output[0 .. space]; 34 | 35 | string tag = getExpectedTag(); 36 | enforce(tag == output, "Tag was named " ~ tag ~ " but serve-d reported version " ~ output); 37 | writeln("serve-d version is ", output, ", same as released."); 38 | } 39 | 40 | string getExpectedTag() 41 | { 42 | string event = environment["GITHUB_EVENT_PATH"]; 43 | auto obj = parseJSON(readText(event).strip); 44 | enforce(obj.type == JSONType.object && "release" in obj.object, 45 | "Not a release event (how did this script even get called at this point?!)"); 46 | 47 | auto release = obj.object["release"]; 48 | auto tag = *enforce("tag_name" in release); 49 | enforce(tag.type == JSONType.string); 50 | 51 | string ret = tag.str; 52 | if (ret.startsWith("v")) 53 | ret = ret[1 .. $]; 54 | 55 | return ret; 56 | } 57 | -------------------------------------------------------------------------------- /dcd/README.md: -------------------------------------------------------------------------------- 1 | DCD client as a library - not depending on libdparse or dsymbol. 2 | 3 | The API for this subpackage is currently not considered stable and may change at any point in time. 4 | -------------------------------------------------------------------------------- /dcd/dub.sdl: -------------------------------------------------------------------------------- 1 | name "dcd" 2 | 3 | dependency "dcd:common" version="~>0.16.0-beta.1" 4 | -------------------------------------------------------------------------------- /dcd/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "msgpack-d": "1.0.5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serve-d", 3 | "description": "Microsoft Language Server Protocol library and D Server", 4 | "license": "MIT", 5 | "copyright": "Copyright © 2017-2023, webfreak", 6 | "dflags": ["-lowmem"], 7 | "dependencies": { 8 | "libddoc": "0.8.0", 9 | "rm-rf": "~>0.1.0", 10 | "diet-complete": "~>0.0.3", 11 | "emsi_containers": "0.9.0", 12 | "serve-d:http": "*", 13 | "serve-d:lsp": "*", 14 | "serve-d:serverbase": "*", 15 | "serve-d:workspace-d": "*", 16 | "fuzzymatch": "~>1.0.0", 17 | "sdlfmt": "~>0.1.1" 18 | }, 19 | "stringImportPaths": [ 20 | "views" 21 | ], 22 | "subPackages": [ 23 | "http", 24 | "protocol", 25 | "lsp", 26 | "serverbase", 27 | "dcd", 28 | "workspace-d" 29 | ], 30 | "versions-windows": ["RequestsSkipSSL"], 31 | "configurations": [ 32 | { 33 | "name": "executable", 34 | "mainSourceFile": "source/app.d", 35 | "targetType": "executable" 36 | }, 37 | { 38 | "name": "unittest", 39 | "mainSourceFile": "source/app.d", 40 | "excludedSourceFiles": [ 41 | "source/app.d", 42 | "source/served/info.d" 43 | ], 44 | "dependencies": { 45 | "silly": "~>1.1.1" 46 | } 47 | }, 48 | { 49 | "name": "unittest-optimized", 50 | "buildOptions": ["optimize", "debugInfo", "unittests"], 51 | "mainSourceFile": "source/app.d", 52 | "excludedSourceFiles": [ 53 | "source/app.d", 54 | "source/served/info.d" 55 | ], 56 | "dependencies": { 57 | "silly": "~>1.1.1" 58 | } 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "automem": "0.6.10", 5 | "botan": "1.12.19", 6 | "botan-math": "1.0.3", 7 | "cachetools": "0.4.1", 8 | "dcd": "0.16.0-beta.2", 9 | "dfmt": "0.15.1", 10 | "diet-complete": "0.0.3", 11 | "diet-ng": "1.8.1", 12 | "dscanner": "0.16.0-beta.4", 13 | "dub": "1.38.0-beta.1", 14 | "emsi_containers": "0.9.0", 15 | "eventcore": "0.9.30", 16 | "fuzzymatch": "1.0.0", 17 | "inifiled": "1.3.3", 18 | "isfreedesktop": "0.1.1", 19 | "libasync": "0.8.6", 20 | "libddoc": "0.8.0", 21 | "libdparse": "0.23.2", 22 | "memutils": "1.0.10", 23 | "mir-algorithm": "3.22.1", 24 | "mir-core": "1.7.1", 25 | "mir-cpuid": "1.2.11", 26 | "mir-ion": "2.3.2", 27 | "mir-linux-kernel": "1.0.1", 28 | "msgpack-d": "1.0.5", 29 | "openssl": "3.3.3", 30 | "openssl-static": "1.0.5+3.0.8", 31 | "requests": "2.1.3", 32 | "rm-rf": "0.1.0", 33 | "sdlfmt": "0.1.1", 34 | "sdlite": "1.1.2", 35 | "silly": "1.1.1", 36 | "standardpaths": "0.8.2", 37 | "stdx-allocator": "2.77.5", 38 | "taggedalgebraic": "0.11.23", 39 | "test_allocator": "0.3.4", 40 | "unit-threaded": "0.10.8", 41 | "vibe-container": "1.3.1", 42 | "vibe-core": "2.8.4", 43 | "vibe-d": "0.9.8", 44 | "xdgpaths": "0.2.5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /editor-emacs.md: -------------------------------------------------------------------------------- 1 | # serve-d with Emacs 2 | 3 | ## Using eglot 4 | 5 | Download or build 6 | 7 | * [serve-d](README.md#Installation) 8 | * [eglot](https://github.com/joaotavora/eglot#1-2-3) 9 | * [d-mode](https://github.com/Emacs-D-Mode-Maintainers/Emacs-D-Mode) 10 | 11 | And edit your `$HOME/.emacs` or `$HOME/.emacs.d/init.el`. 12 | 13 | ```elisp 14 | (add-hook 'd-mode-hook 'eglot-ensure) 15 | (add-to-list 'eglot-server-programs `(d-mode . ("/path/to/your/serve-d"))) 16 | ``` 17 | 18 | OPTIONAL: If you want to customize serve-d (e.g. specifying paths), add 19 | 20 | ```elisp 21 | (setq-default eglot-workspace-configuration 22 | '((:d . (:stdlibPath ("/path/to/dmd-2.098.0/src/phobos" 23 | "/path/to/dmd-2.098.0/src/druntime/src"))))) 24 | ``` 25 | 26 | For per-project config, add [.dir-locals.el](https://github.com/joaotavora/eglot#per-project-server-configuration) in your project root. 27 | -------------------------------------------------------------------------------- /editor-helix.md: -------------------------------------------------------------------------------- 1 | # Using serve-d with [Helix][1] 2 | --- 3 | 4 | Support for D including serve-d is included in Helix as of release 22.12, so 5 | then you should be good to go, as long as serve-d is on your path.) 6 | 7 | This makes use of a [Tree-sitter grammar for D][2] so syntax highlighting and 8 | related functionality is very fast, and of serve-d for other advanced capabilities. 9 | 10 | At the moment configuration options are limited. 11 | 12 | [1]: https://helix-editor.com "Helix Web Site" 13 | [2]: https://github.com/gdamore/tree-sitter-d/ "Tree-sitter Grammar for D" 14 | 15 | -------------------------------------------------------------------------------- /editor-lite-xl.md: -------------------------------------------------------------------------------- 1 | # Using serve-d with Lite-XL 2 | --- 3 | 4 | Requirments: 5 | - lite-xl-lsp Plugin: https://github.com/lite-xl/lite-xl-lsp 6 | 7 | Add the following to your user `init.lua` file (Access it quickly with `Core: Open User Module` command): 8 | 9 | ```lua 10 | local lsp = require "plugins.lsp" 11 | 12 | lsp.add_server { 13 | name = "serve-d", 14 | language = "d", 15 | file_patterns = { "%.d$" }, 16 | command = { "serve-d" }, 17 | incremental_changes = true, 18 | } 19 | ``` 20 | 21 | To add settings, include a table of settings to pass to the LSP: 22 | 23 | ```lua 24 | local lsp = require "plugins.lsp" 25 | 26 | lsp.add_server { 27 | name = "serve-d", 28 | language = "d", 29 | file_patterns = { "%.d$" }, 30 | command = { "serve-d" }, 31 | incremental_changes = true, 32 | settings = { 33 | d = { 34 | enableAutoComplete = false, 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /editor-nova.md: -------------------------------------------------------------------------------- 1 | # Using serve-d with [Nova][1] 2 | --- 3 | 4 | ## Summary 5 | 6 | An extension for Nova called **D-Velop** is [available][2] via the normal 7 | extension facility. This extension utilizes serve-d for LSP functionality. 8 | 9 | Installing this will enable D support to work in Nova. 10 | 11 | It will also enable syntax highlighting, folding, and 12 | indentation features via the Tree-sitter [D grammar][3]. 13 | 14 | ## Requirements 15 | 16 | **D-Velop** will offer to automatically download a suitable 17 | version of serve-d, and by default will notify you when new 18 | releases are available, and offer to update as appropriate. 19 | 20 | You can use your own installation of serve-d as well, but 21 | be advised that served version **0.8.0-beta.1 or newer** is 22 | required to operate with the extension 23 | 24 | [1]: https://nova.app "Nova Editor" 25 | [2]: https://extensions.panic.com/extensions/tech.staysail/tech.staysail.ServeD/ "Serve-D plugin for nova" 26 | [3]: https://github.com/gdamore/tree-sitter-d/ "Tree-sitter Grammar for D" 27 | 28 | -------------------------------------------------------------------------------- /editor-st.md: -------------------------------------------------------------------------------- 1 | # Using serve-d with Sublime Text 2 | --- 3 | 4 | Requirments: 5 | - LSP Package: https://lsp.sublimetext.io/ 6 | 7 | 8 | Client Configuration: 9 | 10 | ```json 11 | { 12 | "clients": { 13 | "serve-d": { 14 | "enabled": true, 15 | "command": ["C:/Users/MY_NAME_HERE/AppData/Roaming/code-d/bin/serve-d.exe"], 16 | "selector": "source.d", 17 | "settings": { 18 | "d.dcdServerPath": "C:/Users/MY_NAME_HERE/AppData/Roaming/code-d/bin/dcd-server.exe", 19 | "d.dcdClientPath": "C:/Users/MY_NAME_HERE/AppData/Roaming/code-d/bin/dcd-client.exe", 20 | 21 | // optional settings 22 | "d.servedReleaseChannel": "nightly", 23 | "d.aggressiveUpdate": false, 24 | "d.manyProjectsAction": "load", 25 | "d.enableAutoComplete": true, 26 | "d.completeNoDupes": true, 27 | } 28 | } 29 | } 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /editor-vim.md: -------------------------------------------------------------------------------- 1 | # serve-d with Vim / NeoVim 2 | 3 | ## Using ycmd 4 | 5 | [Download or build serve-d](README.md#Installation) and edit your `.vimrc`. 6 | 7 | ```vimrc 8 | let g:ycm_language_server = [ 9 | \ { 10 | \ 'name': 'd', 11 | \ 'cmdline': ['path/to/your/serve-d/binary'], 12 | \ 'filetypes': ['d'], 13 | \ }] 14 | ``` 15 | 16 | Familiarize yourself with how ycmd handles `.ycm_extra_conf.py` files. 17 | For serve-d, it is important that you give ycmd the LSP arguments in 18 | the right format. The basic setup for establishing autocomplete with 19 | the D standard library needs the path (or, if multiple paths, an array 20 | of them) to your D standard library on your system. 21 | 22 | ```python 23 | def Settings(**kwargs): 24 | lang = kwargs['language'] 25 | if lang == 'd': 26 | return { 'ls': { 27 | 'd': { 'stdlibPath': '/path/to/your/stdlib' } 28 | # In case you need multiple paths, do 29 | # 'd' : { 'stdlibPath': ['/path/to', '/your/stdlibs']} 30 | # For other config options, see README.md 31 | } 32 | } 33 | else: 34 | return {} 35 | ``` 36 | 37 | Put this in a file called `.ycm_extra_conf.py` and save it in your 38 | project root or move it further up the folder hierarchy, if you know 39 | what you are doing. 40 | 41 | ## Using coc's 42 | 43 | ### Using `CocInstall` 44 | 45 | ``` 46 | :CocInstall coc-dlang 47 | ``` 48 | 49 | ### Manually through coc-settings.json 50 | 51 | First [download or build serve-d](README.md#Installation) 52 | 53 | A `coc-settings.json` file looking like this works well (you can open it with `:CocConfig`) 54 | 55 | ```js 56 | { 57 | "languageserver": { 58 | "d": { 59 | "command": "PATH_TO_SERVE_D_EXECUTABLE/serve-d", 60 | "filetypes": ["d"], 61 | "trace.server": "on", 62 | "rootPatterns": ["dub.json", "dub.sdl"], 63 | "initializationOptions": { 64 | }, 65 | "settings": { 66 | } 67 | } 68 | }, 69 | "suggest.autoTrigger": "none", 70 | "suggest.noselect": false 71 | } 72 | ``` 73 | 74 | ## Using nvim-lspconfig 75 | 76 | Neovim has a builtin LSP client and official LSP configs for it, 77 | [here](https://github.com/neovim/nvim-lspconfig). 78 | 79 | After installing `nvim-lspconfig` using your preferred plugin manager, you must 80 | load serve-d, like the following: 81 | 82 | ```lua 83 | require'lspconfig'.serve_d.setup{} 84 | ``` 85 | 86 | You can read more about the setup function through `:help lspconfig-setup` 87 | 88 | ### User Configuration 89 | 90 | `serve-d` comes pre-packaged with some server-specific settings. You can find a description of such settings [here](https://github.com/Pure-D/code-d/blob/50ca8ca2831403d50bac10681df87a77b1af1bc4/package.json#L373). 91 | 92 | For example, let's assume that you want to change the braces style so that they are on the same line as a function definition. 93 | 94 | `dfmt` is used by `serve-d` by default; it allows the option for the `"stroustrup"` style. 95 | 96 | ```lua 97 | require'lspconfig'.serve_d.setup({ 98 | settings = { 99 | dfmt = { 100 | braceStyle = "stroustrup", 101 | }, 102 | }, 103 | }) 104 | ``` 105 | -------------------------------------------------------------------------------- /editor-zed.md: -------------------------------------------------------------------------------- 1 | # Using serve-d with [Zed][1] 2 | 3 | --- 4 | 5 | Support for D including serve-d is included with the Zed "D" plugin. 6 | 7 | This plugin can be installed using the "zed: extensions" command 8 | or the `Zed ... Extensions ...` menu item. 9 | 10 | The plugin will automatically download the most recent release of serve-d. 11 | 12 | This also makes use of a [Tree-sitter grammar for D][2] so syntax highlighting 13 | and related functionality is very fast, and of serve-d for other advanced 14 | capabilities. 15 | 16 | Various settings for serve-d can be made in either your global personal 17 | settings, or in project specific settings. 18 | 19 | The settings object is a JSON file, and serve-d can be added under the 20 | "serve-d" member of the "lsp" top-level settings. 21 | The binary can also be relocated if a custom binary should be used instead. 22 | 23 | An example configuration: 24 | 25 | ```json 26 | "lsp": { 27 | "serve-d": { 28 | "settings": { 29 | "d": { 30 | "enableFormatting": false, 31 | "manyProjectsThreshold": 20 32 | }, 33 | "dscanner": { 34 | "ignoredKeys": ["dscanner.style.long_line"] 35 | } 36 | }, 37 | "binary": { 38 | "path": "/Users/garrett.damore/Projects/serve-d/serve-d" 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | [1]: https://zed.dev "Zed Editor Web Site"" 45 | [2]: https://github.com/gdamore/tree-sitter-d/ "Tree-sitter Grammar for D" 46 | -------------------------------------------------------------------------------- /http/dub.sdl: -------------------------------------------------------------------------------- 1 | name "http" 2 | dependency "requests" version="~>2.1" 3 | -------------------------------------------------------------------------------- /http/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "automem": "0.6.10", 5 | "cachetools": "0.4.1", 6 | "requests": "2.1.3", 7 | "test_allocator": "0.3.4", 8 | "unit-threaded": "0.10.8" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /http/source/served/http.d: -------------------------------------------------------------------------------- 1 | module served.http; 2 | 3 | import core.thread : Fiber; 4 | 5 | import fs = std.file; 6 | import std.conv; 7 | import std.datetime.stopwatch; 8 | import std.exception; 9 | import std.format; 10 | import std.json; 11 | import std.math; 12 | import std.path; 13 | import std.random; 14 | import std.stdio; 15 | import std.string; 16 | import std.traits; 17 | 18 | version (Windows) pragma(lib, "wininet"); 19 | else 20 | { 21 | // only import in this scope 22 | import requests; 23 | } 24 | 25 | struct InteractiveDownload 26 | { 27 | string url; 28 | string title; 29 | string output; 30 | } 31 | 32 | void downloadFileManual(string url, string title, string into, void delegate(string) onLog) 33 | { 34 | File file = File(into, "wb"); 35 | 36 | StopWatch sw; 37 | sw.start(); 38 | 39 | version (Windows) 40 | { 41 | import core.sys.windows.windows : DWORD; 42 | import core.sys.windows.wininet : INTERNET_FLAG_NO_UI, 43 | INTERNET_OPEN_TYPE_PRECONFIG, InternetCloseHandle, InternetOpenA, 44 | InternetOpenUrlA, InternetReadFile, IRF_NO_WAIT, INTERNET_BUFFERSA, 45 | HttpQueryInfoA, HTTP_QUERY_CONTENT_LENGTH; 46 | 47 | auto handle = enforce(InternetOpenA("serve-d_release-downloader/Pure-D/serve-d", 48 | INTERNET_OPEN_TYPE_PRECONFIG, null, null, 0), "Failed to open internet handle"); 49 | scope (exit) 50 | InternetCloseHandle(handle); 51 | 52 | auto obj = enforce(InternetOpenUrlA(handle, cast(const(char)*) url.toStringz, 53 | null, 0, INTERNET_FLAG_NO_UI, 0), "Opening URL failed"); 54 | scope (exit) 55 | InternetCloseHandle(obj); 56 | 57 | scope ubyte[] buffer = new ubyte[50 * 1024]; 58 | 59 | long maxLen; 60 | DWORD contentLengthLength = 16; 61 | DWORD index = 0; 62 | if (HttpQueryInfoA(obj, HTTP_QUERY_CONTENT_LENGTH, buffer.ptr, &contentLengthLength, &index)) 63 | maxLen = (cast(char[]) buffer[0 .. contentLengthLength]).strip.to!long; 64 | 65 | long received; 66 | while (true) 67 | { 68 | DWORD read; 69 | if (!InternetReadFile(obj, buffer.ptr, cast(DWORD) buffer.length, &read)) 70 | throw new Exception("Failed to read from internet file"); 71 | 72 | if (read == 0) 73 | break; 74 | 75 | file.rawWrite(buffer[0 .. read]); 76 | received += read; 77 | 78 | if (sw.peek >= 1.seconds) 79 | { 80 | sw.reset(); 81 | if (maxLen > 0) 82 | onLog(format!"%s %s / %s (%.1f %%)"(title, humanSize(received), 83 | humanSize(maxLen), received / cast(float) maxLen * 100)); 84 | else 85 | onLog(format!"%s %s / ???"(title, humanSize(received))); 86 | Fiber.yield(); 87 | } 88 | } 89 | } 90 | else 91 | { 92 | auto req = Request(); 93 | req.useStreaming = true; 94 | 95 | auto res = req.get(url); 96 | 97 | foreach (part; res.receiveAsRange()) 98 | { 99 | file.rawWrite(part); 100 | 101 | if (sw.peek >= 1.seconds) 102 | { 103 | sw.reset(); 104 | onLog(format!"%s %s / %s (%.1f %%)"(title, humanSize(res.contentReceived), 105 | humanSize(res.contentLength), res.contentReceived / cast(float) res.contentLength * 100)); 106 | Fiber.yield(); 107 | } 108 | } 109 | } 110 | } 111 | 112 | string humanSize(T)(T bytes) if (isIntegral!T) 113 | { 114 | static immutable string prefixes = "kMGTPE"; 115 | 116 | if (bytes < 1024) 117 | return text(bytes, " B"); 118 | int exp = cast(int)(log2(double(bytes)) / 8); // 8 = log2(1024) 119 | return format!"%.1f %siB"(bytes / cast(float) pow(1024, exp), prefixes[exp - 1]); 120 | } 121 | -------------------------------------------------------------------------------- /index-utils/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /index-utils 6 | index-utils.so 7 | index-utils.dylib 8 | index-utils.dll 9 | index-utils.a 10 | index-utils.lib 11 | index-utils-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /index-utils/dub.sdl: -------------------------------------------------------------------------------- 1 | name "index-utils" 2 | sourceFiles "source/app.d" \ 3 | "../workspace-d/source/workspaced/index_format.d" \ 4 | "../workspace-d/source/workspaced/helpers.d" 5 | dependency "msgpack-d" version="~>1.0.4" 6 | dependency "standardpaths" version="~>0.8.2" 7 | -------------------------------------------------------------------------------- /index-utils/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "isfreedesktop": "0.1.1", 5 | "msgpack-d": "1.0.4", 6 | "standardpaths": "0.8.2", 7 | "xdgpaths": "0.2.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /index-utils/source/app.d: -------------------------------------------------------------------------------- 1 | import std.algorithm; 2 | import std.stdio; 3 | import workspaced.index_format; 4 | 5 | void main() 6 | { 7 | IndexCache index = IndexCache.load; 8 | writeln("index: ", index.fileName); 9 | writeln("indexed files: ", index.getIndexedFiles.length); 10 | auto files = cast(IndexCache.IndexedFile[])index.getIndexedFiles; 11 | foreach (file; files.sort!"a.fileName ut[.exe]) 6 | 7 | Target aliasTarget(string aliasName, alias target)() { 8 | import std.algorithm: map; 9 | // Using a leaf target with `$builddir/` outputs as dependency 10 | // yields the expected relative target names for Ninja/make. 11 | return Target.phony(aliasName, "", Target(target.rawOutputs.map!(o => "$builddir/" ~ o), "")); 12 | } 13 | 14 | // Add a `default` convenience alias for the `dub build` target. 15 | // Especially useful for Ninja (`ninja default ut` to build default & test targets in parallel). 16 | alias defaultTarget = aliasTarget!("default", buildTarget); 17 | 18 | version (Windows) { 19 | // Windows: extra `ut` convenience alias for `ut.exe` 20 | alias utTarget = aliasTarget!("ut", testTarget); 21 | mixin build!(buildTarget, optional!testTarget, optional!defaultTarget, optional!utTarget); 22 | } else { 23 | mixin build!(buildTarget, optional!testTarget, optional!defaultTarget); 24 | } 25 | -------------------------------------------------------------------------------- /release.bat: -------------------------------------------------------------------------------- 1 | rem Building & compressing serve-d for release inside a virtual machine with Windows 8 or above 2 | 3 | pushd %~dp0 4 | 5 | @if not exist version.txt ( 6 | echo. 7 | echo !-- Error: version.txt is missing :/ 8 | echo. 9 | pause 10 | popd 11 | goto :eof 12 | ) 13 | 14 | rem This will sync this repo with the folder %SystemDrive%\buildsd 15 | robocopy . %SystemDrive%\buildsd /MIR /XA:SH /XD .* /XF .* /XF *.zip 16 | pushd %SystemDrive%\buildsd 17 | 18 | set /p Version=&1 | grep -oh "serve-d standalone v[0-9]*\.[0-9]*\.[0-9]*" | sed "s/serve-d standalone v//") 5 | echo $VERSION > version.txt 6 | echo $VERSION 7 | tar cfJ serve-d_$VERSION-linux-x86_64.tar.xz serve-d 8 | tar czf serve-d_$VERSION-linux-x86_64.tar.gz serve-d 9 | 10 | -------------------------------------------------------------------------------- /serverbase/dub.sdl: -------------------------------------------------------------------------------- 1 | name "serverbase" 2 | 3 | dependency "serve-d:lsp" path=".." 4 | -------------------------------------------------------------------------------- /serverbase/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "mir-algorithm": "3.22.1", 5 | "mir-core": "1.7.1", 6 | "mir-cpuid": "1.2.11", 7 | "mir-ion": "2.3.2", 8 | "serve-d": {"path":".."}, 9 | "silly": "1.1.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /serverbase/source/served/utils/async.d: -------------------------------------------------------------------------------- 1 | module served.utils.async; 2 | 3 | import core.sync.mutex : Mutex; 4 | import core.time : Duration; 5 | 6 | import served.utils.fibermanager; 7 | 8 | import std.datetime.stopwatch : msecs, StopWatch; 9 | import std.experimental.logger; 10 | 11 | __gshared void delegate(string name, void delegate(), int pages, string file, int line) spawnFiberImpl; 12 | __gshared int defaultFiberPages = 20; 13 | __gshared int timeoutID; 14 | __gshared Timeout[] timeouts; 15 | __gshared Mutex timeoutsMutex; 16 | 17 | void spawnFiber(string name, void delegate() cb, string file = __FILE__, int line = __LINE__) 18 | { 19 | spawnFiber(name, cb, defaultFiberPages, file, line); 20 | } 21 | 22 | void spawnFiber(string name, void delegate() cb, int pages, string file = __FILE__, int line = __LINE__) 23 | { 24 | if (spawnFiberImpl) 25 | spawnFiberImpl(name, cb, pages, file, line); 26 | else 27 | setImmediate(cb); 28 | } 29 | 30 | // Called at most 100x per second 31 | void parallelMain() 32 | { 33 | timeoutsMutex = new Mutex; 34 | void delegate()[32] callsBuf; 35 | void delegate()[] calls; 36 | while (true) 37 | { 38 | synchronized (timeoutsMutex) 39 | foreach_reverse (i, ref timeout; timeouts) 40 | { 41 | if (timeout.sw.peek >= timeout.timeout) 42 | { 43 | timeout.sw.stop(); 44 | trace("Calling timeout"); 45 | callsBuf[calls.length] = timeout.callback; 46 | calls = callsBuf[0 .. calls.length + 1]; 47 | if (timeouts.length > 1) 48 | timeouts[i] = timeouts[$ - 1]; 49 | timeouts.length--; 50 | 51 | if (calls.length >= callsBuf.length) 52 | break; 53 | } 54 | } 55 | 56 | foreach (call; calls) 57 | call(); 58 | 59 | callsBuf[] = null; 60 | calls = null; 61 | Fiber.yield(); 62 | } 63 | } 64 | 65 | struct Timeout 66 | { 67 | StopWatch sw; 68 | Duration timeout; 69 | void delegate() callback; 70 | int id; 71 | } 72 | 73 | int setTimeout(void delegate() callback, int ms) 74 | { 75 | return setTimeout(callback, ms.msecs); 76 | } 77 | 78 | void setImmediate(void delegate() callback) 79 | { 80 | setTimeout(callback, 0); 81 | } 82 | 83 | int setTimeout(void delegate() callback, Duration timeout) 84 | { 85 | trace("Setting timeout for ", timeout); 86 | Timeout to; 87 | to.timeout = timeout; 88 | to.callback = callback; 89 | to.sw.start(); 90 | synchronized (timeoutsMutex) 91 | { 92 | to.id = ++timeoutID; 93 | timeouts ~= to; 94 | } 95 | return to.id; 96 | } 97 | 98 | void clearTimeout(int id) 99 | { 100 | synchronized (timeoutsMutex) 101 | foreach_reverse (i, ref timeout; timeouts) 102 | { 103 | if (timeout.id == id) 104 | { 105 | timeout.sw.stop(); 106 | if (timeouts.length > 1) 107 | timeouts[i] = timeouts[$ - 1]; 108 | timeouts.length--; 109 | return; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /serverbase/source/served/utils/fibermanager.d: -------------------------------------------------------------------------------- 1 | module served.utils.fibermanager; 2 | 3 | // debug = Fibers; 4 | 5 | import core.thread; 6 | import core.time; 7 | 8 | import std.algorithm; 9 | import std.experimental.logger; 10 | import std.range; 11 | 12 | import served.utils.memory; 13 | 14 | public import core.thread : Fiber, Thread; 15 | 16 | struct FiberManager 17 | { 18 | static struct FiberInfo 19 | { 20 | string name; 21 | Fiber fiber; 22 | MonoTime queueTime, startTime, endTime; 23 | Duration timeSpent; 24 | int numSteps = 0; 25 | FiberManager* nested; /// for `joinAll` calls 26 | 27 | alias fiber this; 28 | } 29 | 30 | static FiberManager* currentFiberManager; 31 | 32 | ref FiberInfo currentFiber() 33 | { 34 | assert(currentFiberIndex != -1, "not inside a fiber in this FiberManager right now!"); 35 | return fibers[currentFiberIndex]; 36 | } 37 | 38 | const(FiberInfo[]) fiberInfos() const 39 | { 40 | return fibers; 41 | } 42 | 43 | private size_t currentFiberIndex = -1; 44 | private FiberInfo[] fibers; 45 | 46 | FiberInfo[128] recentlyEnded; 47 | size_t recentlyEndedIndex; 48 | 49 | void call() 50 | { 51 | auto previousFiberManager = currentFiberManager; 52 | currentFiberManager = &this; 53 | scope (exit) 54 | currentFiberManager = previousFiberManager; 55 | 56 | MonoTime now; 57 | size_t[] toRemove; 58 | foreach (i, ref fiber; fibers) 59 | { 60 | if (fiber.state == Fiber.State.TERM) 61 | toRemove ~= i; 62 | else 63 | { 64 | currentFiberIndex = i; 65 | scope (exit) 66 | currentFiberIndex = -1; 67 | now = MonoTime.currTime; 68 | if (fiber.numSteps == 0) 69 | fiber.startTime = now; 70 | fiber.call(); 71 | auto now2 = MonoTime.currTime; 72 | fiber.numSteps++; 73 | fiber.timeSpent += (now2 - now); 74 | now = now2; 75 | } 76 | } 77 | 78 | if (toRemove.length && now is MonoTime.init) 79 | now = MonoTime.currTime; 80 | 81 | foreach_reverse (i; toRemove) 82 | { 83 | debug (Fibers) 84 | tracef("Releasing fiber %s", cast(void*) fibers[i]); 85 | auto rei = recentlyEndedIndex++; 86 | if (recentlyEndedIndex >= recentlyEnded.length) 87 | recentlyEndedIndex = 0; 88 | if (recentlyEnded[rei] !is FiberInfo.init) 89 | destroyUnset(recentlyEnded[rei].fiber); 90 | move(fibers[i], recentlyEnded[rei]); 91 | recentlyEnded[rei].endTime = now; 92 | fibers = fibers.remove(i); 93 | } 94 | } 95 | 96 | size_t length() const @property 97 | { 98 | return fibers.length; 99 | } 100 | 101 | /// Makes a fiber call alongside other fibers with this manager. This transfers the full memory ownership to the manager. 102 | /// Fibers should no longer be accessed when terminating. 103 | void put(string name, Fiber fiber, string file = __FILE__, int line = __LINE__) 104 | { 105 | debug (Fibers) 106 | tracef("Putting fiber %s in %s:%s", cast(void*) fiber, file, line); 107 | fibers.assumeSafeAppend ~= FiberInfo(name, fiber, MonoTime.currTime); 108 | } 109 | } 110 | 111 | private template hasInputRanges(Args...) 112 | { 113 | static if (Args.length == 0) 114 | enum hasInputRanges = false; 115 | else static if (isInputRange!(Args[$ - 1])) 116 | enum hasInputRanges = true; 117 | else 118 | enum hasInputRanges = hasInputRanges!(Args[0 .. $ - 1]); 119 | } 120 | 121 | // ridiculously high fiber size (192 KiB per fiber to create), but for parsing big files this is needed to not segfault in libdparse 122 | void joinAll(size_t fiberSize = 4096 * 48, string caller = __FUNCTION__, Fibers...)(Fibers fibers) 123 | { 124 | import std.conv : to; 125 | 126 | FiberManager f; 127 | enum anyInputRanges = hasInputRanges!Fibers; 128 | auto now = MonoTime.currTime; 129 | static if (anyInputRanges) 130 | { 131 | FiberManager.FiberInfo[] converted; 132 | converted.reserve(Fibers.length); 133 | void addFiber(string name, Fiber fiber) 134 | { 135 | converted ~= FiberManager.FiberInfo(name, fiber, now); 136 | } 137 | } 138 | else 139 | { 140 | int convertedIndex; 141 | FiberManager.FiberInfo[Fibers.length] converted; 142 | 143 | void addFiber(string name, Fiber fiber) 144 | { 145 | converted[convertedIndex++] = FiberManager.FiberInfo(name, fiber, now); 146 | } 147 | } 148 | 149 | static foreach (i, fiber; fibers) 150 | {{ 151 | static immutable fiberName = caller ~ ".joinAll[" ~ i.stringof ~ "]"; 152 | 153 | static if (isInputRange!(typeof(fiber))) 154 | { 155 | foreach (j, fib; fiber) 156 | { 157 | string subName = fiberName ~ "[" ~ j.to!string ~ "]"; 158 | static if (is(typeof(fib) : Fiber)) 159 | addFiber(subName, fib); 160 | else static if (__traits(hasMember, fib, "toFiber")) 161 | addFiber(subName, fib.toFiber); 162 | else static if (__traits(hasMember, fib, "getYield")) 163 | addFiber(subName, new Fiber(&fib.getYield, fiberSize)); 164 | else 165 | addFiber(subName, new Fiber(fib, fiberSize)); 166 | } 167 | } 168 | else 169 | { 170 | static if (is(typeof(fiber) : Fiber)) 171 | addFiber(fiberName, fiber); 172 | else static if (__traits(hasMember, fiber, "toFiber")) 173 | addFiber(fiberName, fiber.toFiber); 174 | else static if (__traits(hasMember, fiber, "getYield")) 175 | addFiber(fiberName, new Fiber(&fiber.getYield, fiberSize)); 176 | else 177 | addFiber(fiberName, new Fiber(fiber, fiberSize)); 178 | } 179 | }} 180 | 181 | FiberManager.currentFiberManager.currentFiber.nested = &f; 182 | scope (exit) 183 | FiberManager.currentFiberManager.currentFiber.nested = null; 184 | 185 | f.fibers = converted[]; 186 | while (f.length) 187 | { 188 | f.call(); 189 | Fiber.yield(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /serverbase/source/served/utils/memory.d: -------------------------------------------------------------------------------- 1 | module served.utils.memory; 2 | 3 | /// Calls `destro!false` on the value or just `destroy` if not supported. 4 | /// Makes the value undefined/unset after calling so it shouldn't be used anymore. 5 | void destroyUnset(T)(ref T value) 6 | if (__traits(compiles, destroy!false(value)) || __traits(compiles, destroy(value))) 7 | { 8 | static if (__traits(compiles, destroy!false(value))) 9 | destroy!false(value); 10 | else 11 | destroy(value); 12 | } 13 | 14 | /// ditto 15 | deprecated("Type doesn't support to be destroyed in this D version") void destroyUnset(T)(ref T value) 16 | if (!__traits(compiles, destroy!false(value)) && !__traits(compiles, destroy(value))) 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /source/crash_handler.d: -------------------------------------------------------------------------------- 1 | module crash_handler; 2 | 3 | version (CRuntime_Musl) 4 | enum BacktraceHandler = false; 5 | else version (linux) 6 | enum BacktraceHandler = true; 7 | else version (OSX) 8 | enum BacktraceHandler = true; 9 | else 10 | enum BacktraceHandler = false; 11 | 12 | static if (BacktraceHandler) 13 | { 14 | extern (C) int backtrace(void** buffer, int size) nothrow @nogc @system; 15 | extern (C) void backtrace_symbols_fd(const(void*)* buffer, int size, int fd) nothrow @nogc @system; 16 | 17 | extern (C) void backtrace_handler(int sig) nothrow @nogc @system 18 | { 19 | import core.sys.posix.stdlib : exit; 20 | import core.sys.posix.stdio : fprintf, fflush, stderr; 21 | 22 | void*[100] buffer; 23 | auto size = backtrace(buffer.ptr, cast(int) buffer.length); 24 | 25 | fprintf(stderr, "Error: signal %d:\n", sig); 26 | backtrace_symbols_fd(buffer.ptr, size, 2); 27 | fflush(stderr); 28 | exit(-sig); 29 | } 30 | 31 | void registerErrorHandlers() 32 | { 33 | import core.sys.posix.stdio : fprintf, stderr; 34 | import core.sys.posix.signal : signal, SIGABRT, SIGALRM, SIGILL, SIGINT, 35 | SIGKILL, SIGPIPE, SIGSEGV, SIGTRAP; 36 | 37 | static foreach (sig; [ 38 | SIGABRT, SIGALRM, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGSEGV, SIGTRAP 39 | ]) 40 | signal(sig, &backtrace_handler); 41 | 42 | // this is print on every invocation of serve-d, so we only print this in unittests and assume it works elsewhere 43 | version (unittest) 44 | fprintf(stderr, "Registered backtrace signal handlers\n"); 45 | } 46 | 47 | shared static this() 48 | { 49 | registerErrorHandlers(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /source/served/commands/code_lens.d: -------------------------------------------------------------------------------- 1 | module served.commands.code_lens; 2 | 3 | import served.commands.code_actions; 4 | 5 | import served.extension; 6 | import served.types; 7 | import served.utils.fibermanager; 8 | 9 | import workspaced.api; 10 | import workspaced.coms; 11 | 12 | import core.time : minutes, msecs; 13 | 14 | import std.algorithm : startsWith; 15 | import std.conv : to; 16 | import std.datetime.stopwatch : StopWatch; 17 | import std.datetime.systime : Clock, SysTime; 18 | import std.regex : matchAll; 19 | 20 | @protocolMethod("textDocument/codeLens") 21 | CodeLens[] provideCodeLens(CodeLensParams params) 22 | { 23 | auto document = documents[params.textDocument.uri]; 24 | string file = document.uri.uriToFile; 25 | if (document.getLanguageId != "d") 26 | return []; 27 | CodeLens[] ret; 28 | if (workspace(params.textDocument.uri).config.d.enableDMDImportTiming) 29 | { 30 | size_t lastIndex = size_t.max; 31 | Position lastPosition; 32 | 33 | foreach (match; document.rawText.matchAll(importRegex)) 34 | { 35 | size_t index = match.pre.length; 36 | auto pos = document.movePositionBytes(lastPosition, lastIndex, index); 37 | lastIndex = index; 38 | lastPosition = pos; 39 | 40 | ret ~= CodeLens(TextRange(pos), Optional!Command.init, 41 | JsonValue([ 42 | "type": JsonValue("importcompilecheck"), 43 | "code": JsonValue(match.hit.idup), 44 | "module": JsonValue(match[1].idup), 45 | "file": JsonValue(file) 46 | ]).opt); 47 | } 48 | } 49 | return ret; 50 | } 51 | 52 | @protocolMethod("codeLens/resolve") 53 | CodeLens resolveCodeLens(CodeLens lens) 54 | { 55 | if (lens.data.isNone || 56 | lens.data.deref.kind != JsonValue.Kind.object) 57 | throw new Exception("Invalid Lens Object"); 58 | 59 | auto lensData = lens.data.deref.get!(StringMap!JsonValue); 60 | auto type = lensData.get("type", JsonValue("")) 61 | .match!((string s) => s, _ => ""); 62 | switch (type) 63 | { 64 | case "importcompilecheck": 65 | try 66 | { 67 | auto code = lensData.get("code", JsonValue(null)) 68 | .match!((string s) => s, _ => ""); 69 | if (!code.length) 70 | throw new Exception("No valid code provided"); 71 | 72 | auto module_ = lensData.get("module", JsonValue(null)) 73 | .match!((string s) => s, _ => ""); 74 | if (!module_.length) 75 | throw new Exception("No valid module provided"); 76 | 77 | auto file = lensData.get("file", JsonValue(null)) 78 | .match!((string s) => s, _ => ""); 79 | if (!file.length) 80 | throw new Exception("No valid file provided"); 81 | 82 | int decMs = getImportCompilationTime(code, module_, file); 83 | lens.command = Command((decMs < 10 84 | ? "no noticable effect" : "~" ~ decMs.to!string ~ "ms") ~ " for importing this"); 85 | return lens; 86 | } 87 | catch (Exception) 88 | { 89 | lens.command = Command.init; 90 | return lens; 91 | } 92 | default: 93 | throw new Exception("Unknown lens type"); 94 | } 95 | } 96 | 97 | bool importCompilationTimeRunning; 98 | int getImportCompilationTime(string code, string module_, string file) 99 | { 100 | import std.math : round; 101 | 102 | static struct CompileCache 103 | { 104 | SysTime at; 105 | string code; 106 | int ret; 107 | } 108 | 109 | static CompileCache[] cache; 110 | 111 | auto now = Clock.currTime; 112 | 113 | foreach_reverse (i, exist; cache) 114 | { 115 | if (exist.code != code) 116 | continue; 117 | if (now - exist.at < (exist.ret >= 500 ? 20.minutes : exist.ret >= 30 ? 5.minutes 118 | : 2.minutes) || module_.startsWith("std.")) 119 | return exist.ret; 120 | else 121 | { 122 | cache[i] = cache[$ - 1]; 123 | cache.length--; 124 | } 125 | } 126 | 127 | while (importCompilationTimeRunning) 128 | Fiber.yield(); 129 | importCompilationTimeRunning = true; 130 | scope (exit) 131 | importCompilationTimeRunning = false; 132 | // run blocking so we don't compute multiple in parallel 133 | auto ret = backend.best!DMDComponent(file).measureSync(code, null, 20, 500); 134 | if (!ret.success) 135 | throw new Exception("Compilation failed"); 136 | auto msecs = cast(int) round(ret.duration.total!"msecs" / 5.0) * 5; 137 | cache ~= CompileCache(now, code, msecs); 138 | StopWatch sw; 139 | sw.start(); 140 | while (sw.peek < 100.msecs) // pass through requests for 100ms 141 | Fiber.yield(); 142 | return msecs; 143 | } 144 | -------------------------------------------------------------------------------- /source/served/commands/color.d: -------------------------------------------------------------------------------- 1 | module served.commands.color; 2 | 3 | import std.conv; 4 | import std.format; 5 | import std.regex; 6 | 7 | import served.types; 8 | 9 | static immutable colorRegex = ctRegex!`#([0-9a-fA-F]{2}){3,4}|"#([0-9a-fA-F]{2}){3,4}"`; 10 | 11 | @protocolMethod("textDocument/documentColor") 12 | ColorInformation[] provideDocumentColor(DocumentColorParams params) 13 | { 14 | Document document = documents[params.textDocument.uri]; 15 | if (document.getLanguageId != "dml") 16 | return null; 17 | 18 | ColorInformation[] ret; 19 | 20 | { 21 | // if we ever want to make serve-d multi-threaded, want to lock here 22 | // document.lock(); 23 | // scope (exit) 24 | // document.unlock(); 25 | 26 | size_t cacheBytes; 27 | Position cachePos; 28 | foreach (match; matchAll(document.rawText, colorRegex)) 29 | { 30 | const(char)[] text = match.hit; 31 | if (text[0] == '"') 32 | text = text[1 .. $ - 1]; 33 | assert(text[0] == '#', "broken regex match"); 34 | text = text[1 .. $]; 35 | assert(text.length == 6 || text.length == 8, "broken regex match"); 36 | 37 | TextRange range; 38 | cachePos = range.start = document.movePositionBytes(cachePos, cacheBytes, cacheBytes = match.pre.length); 39 | cachePos = range.end = document.movePositionBytes(cachePos, cacheBytes, cacheBytes = match.pre.length + match.hit.length); 40 | 41 | Color color; 42 | if (text.length == 8) 43 | { 44 | color.alpha = text[0 .. 2].to!int(16) / 255.0; 45 | text = text[2 .. $]; 46 | } 47 | color.red = text[0 .. 2].to!int(16) / 255.0; 48 | color.green = text[2 .. 4].to!int(16) / 255.0; 49 | color.blue = text[4 .. 6].to!int(16) / 255.0; 50 | ret ~= ColorInformation(range, color); 51 | } 52 | } 53 | 54 | return ret; 55 | } 56 | 57 | @protocolMethod("textDocument/colorPresentation") 58 | ColorPresentation[] provideColorPresentations(ColorPresentationParams params) 59 | { 60 | Document document = documents[params.textDocument.uri]; 61 | if (document.getLanguageId != "dml") 62 | return null; 63 | 64 | // only hex supported 65 | string hex; 66 | if (params.color.alpha != 1) 67 | hex = format!"#%02x%02x%02x%02x"( 68 | cast(int)(params.color.alpha * 255), 69 | cast(int)(params.color.red * 255), 70 | cast(int)(params.color.green * 255), 71 | cast(int)(params.color.blue * 255) 72 | ); 73 | else 74 | hex = format!"#%02x%02x%02x"( 75 | cast(int)(params.color.red * 255), 76 | cast(int)(params.color.green * 255), 77 | cast(int)(params.color.blue * 255) 78 | ); 79 | 80 | return [ColorPresentation(hex)]; 81 | } 82 | -------------------------------------------------------------------------------- /source/served/commands/definition.d: -------------------------------------------------------------------------------- 1 | module served.commands.definition; 2 | 3 | import served.extension; 4 | import served.types; 5 | import served.utils.ddoc; 6 | 7 | import workspaced.api; 8 | import workspaced.com.dcd; 9 | import workspaced.coms; 10 | 11 | import std.experimental.logger; 12 | import std.path : buildPath, isAbsolute; 13 | import std.string; 14 | 15 | import fs = std.file; 16 | import io = std.stdio; 17 | 18 | struct DeclarationInfo 19 | { 20 | string declaration; 21 | Location location; 22 | } 23 | 24 | DeclarationInfo findDeclarationImpl(WorkspaceD.Instance instance, scope ref Document doc, 25 | int bytes, bool includeDefinition) 26 | { 27 | auto result = instance.get!DCDComponent.findDeclaration(doc.rawText, bytes).getYield; 28 | if (result == DCDDeclaration.init) 29 | return DeclarationInfo.init; 30 | 31 | auto uri = doc.uri; 32 | if (result.file != "stdin") 33 | { 34 | if (isAbsolute(result.file)) 35 | uri = uriFromFile(result.file); 36 | else 37 | uri = null; 38 | } 39 | 40 | trace("raw declaration result: ", uri, ":", result.position); 41 | 42 | size_t byteOffset = cast(size_t) result.position; 43 | bool tempLoad; 44 | auto found = documents.getOrFromFilesystem(uri, tempLoad); 45 | DeclarationInfo ret; 46 | if (found.uri) 47 | { 48 | TextRange range; 49 | if (instance.has!DCDExtComponent) 50 | { 51 | auto dcdext = instance.get!DCDExtComponent; 52 | range = getDeclarationRange(dcdext, found, byteOffset, includeDefinition); 53 | 54 | trace(" declaration refined to ", range); 55 | } 56 | if (range is TextRange.init) 57 | { 58 | auto pos = found.bytesToPosition(byteOffset); 59 | range = TextRange(pos, pos); 60 | } 61 | if (range !is TextRange.init) 62 | { 63 | ret.declaration = found.sliceRawText(range).idup; 64 | 65 | // TODO: resolve auto type 66 | 67 | if (instance.has!DfmtComponent) 68 | { 69 | trace("Formatting declaration ", [ret.declaration]); 70 | 71 | ret.declaration = instance.get!DfmtComponent.formatSync(ret.declaration, 72 | [ 73 | "--keep_line_breaks=false", 74 | "--single_indent=true", 75 | "--indent_size=2", 76 | "--indent_style=space", 77 | "--max_line_length=60", 78 | "--soft_max_line_length=50", 79 | "--end_of_line=lf" 80 | ]); 81 | } 82 | } 83 | if (tempLoad) 84 | documents.unloadDocument(uri); 85 | ret.location = Location(uri, range); 86 | } 87 | 88 | return ret; 89 | } 90 | 91 | TextRange getDeclarationRange(DCDExtComponent dcdext, ref Document doc, size_t byteOffset, bool includeDefinition) 92 | { 93 | auto range = dcdext.getDeclarationRange(doc.rawText, byteOffset, includeDefinition); 94 | if (range == typeof(range).init) 95 | return TextRange.init; 96 | return doc.byteRangeToTextRange(range); 97 | } 98 | 99 | @protocolMethod("textDocument/definition") 100 | ArrayOrSingle!Location provideDefinition(TextDocumentPositionParams params) 101 | { 102 | auto instance = activeInstance = backend.getBestInstance!DCDComponent( 103 | params.textDocument.uri.uriToFile); 104 | if (!instance) 105 | return ArrayOrSingle!Location.init; 106 | 107 | auto document = documents[params.textDocument.uri]; 108 | if (document.getLanguageId != "d") 109 | return ArrayOrSingle!Location.init; 110 | 111 | auto result = findDeclarationImpl(instance, document, 112 | cast(int) document.positionToBytes(params.position), true); 113 | if (result == DeclarationInfo.init) 114 | return ArrayOrSingle!Location.init; 115 | 116 | return ArrayOrSingle!Location(result.location); 117 | } 118 | 119 | @protocolMethod("textDocument/hover") 120 | Hover provideHover(TextDocumentPositionParams params) 121 | { 122 | auto instance = activeInstance = backend.getBestInstance!DCDComponent( 123 | params.textDocument.uri.uriToFile); 124 | if (!instance) 125 | return Hover.init; 126 | 127 | auto document = documents[params.textDocument.uri]; 128 | if (document.getLanguageId != "d") 129 | return Hover.init; 130 | 131 | DCDComponent dcd = instance.get!DCDComponent(); 132 | 133 | auto docs = dcd.getDocumentation(document.rawText, 134 | cast(int) document.positionToBytes(params.position)).getYield; 135 | auto marked = docs.ddocToMarked; 136 | 137 | try 138 | { 139 | auto result = findDeclarationImpl(instance, document, 140 | cast(int) document.positionToBytes(params.position), false); 141 | result.declaration = result.declaration.strip; 142 | if (result.declaration.length) 143 | marked = MarkedString(result.declaration, "d") ~ marked; 144 | } 145 | catch (Exception e) 146 | { 147 | } 148 | 149 | Hover ret; 150 | ret.contents = marked; 151 | return ret; 152 | } 153 | -------------------------------------------------------------------------------- /source/served/commands/file_search.d: -------------------------------------------------------------------------------- 1 | module served.commands.file_search; 2 | 3 | import served.extension; 4 | import served.lsp.filereader; 5 | import served.types; 6 | 7 | import workspaced.api; 8 | import workspaced.coms; 9 | import workspaced.helpers; 10 | 11 | import std.algorithm : endsWith, sort, startsWith, uniq; 12 | import std.path : baseName, buildNormalizedPath, buildPath, isAbsolute, stripExtension; 13 | import std.string : makeTransTable, translate; 14 | 15 | import fs = std.file; 16 | import io = std.stdio; 17 | 18 | @protocolMethod("served/searchFile") 19 | string[] searchFile(string query) 20 | { 21 | if (!query.length) 22 | return null; 23 | 24 | if (query.isAbsolute) 25 | { 26 | if (fs.exists(query)) 27 | return [query]; 28 | else 29 | return null; 30 | } 31 | 32 | string[] ret; 33 | string[] importFiles, importPaths; 34 | importPaths = selectedWorkspace.stdlibPath(); 35 | 36 | foreach (instance; backend.instances) 37 | { 38 | importFiles ~= instance.importFiles; 39 | importPaths ~= instance.importPaths; 40 | } 41 | 42 | importFiles.sort!"a= 0) 105 | continue; 106 | if (fs.exists(file) && fs.isFile(file)) 107 | { 108 | auto fileMod = backend.get!ModulemanComponent.moduleName( 109 | cast(string) file.readCodeWithBuffer(buffer)); 110 | if (fileMod.startsWith("std")) 111 | { 112 | modFileCache[fileMod] = file; 113 | cachedModuleFiles.insertSorted(file); 114 | } 115 | if (fileMod == module_) 116 | ret ~= file; 117 | } 118 | } 119 | foreach (dir; importPaths.uniq) 120 | { 121 | if (fs.exists(dir) && fs.isDir(dir)) 122 | foreach (filename; fs.dirEntries(dir, fs.SpanMode.breadth)) 123 | if (filename.isFile) 124 | { 125 | auto file = buildPath(dir, filename); 126 | if (cachedModuleFiles.binarySearch(file) >= 0) 127 | continue; 128 | auto fileMod = moduleNameForFile(file, dir, buffer); 129 | if (fileMod.startsWith("std")) 130 | { 131 | modFileCache[fileMod] = file; 132 | cachedModuleFiles.insertSorted(file); 133 | } 134 | if (fileMod == module_) 135 | ret ~= file; 136 | } 137 | } 138 | 139 | return ret; 140 | } 141 | 142 | private string moduleNameForFile(string file, string dir, ref ubyte[] buffer) 143 | { 144 | auto ret = backend.get!ModulemanComponent.moduleName( 145 | cast(string) file.readCodeWithBuffer(buffer)); 146 | if (ret.length) 147 | return ret; 148 | file = buildNormalizedPath(file); 149 | dir = buildNormalizedPath(dir); 150 | if (file.startsWith(dir)) 151 | return file[dir.length .. $].stripExtension.translate(makeTransTable("/\\", "..")); 152 | else 153 | return baseName(file).stripExtension; 154 | } 155 | -------------------------------------------------------------------------------- /source/served/commands/folding.d: -------------------------------------------------------------------------------- 1 | module served.commands.folding; 2 | 3 | import std.array; 4 | 5 | import served.types; 6 | import served.types : FoldingRange; 7 | 8 | import workspaced.api; 9 | import workspaced.com.dcdext; 10 | 11 | @protocolMethod("textDocument/foldingRange") 12 | Nullable!(FoldingRange[]) provideFoldingRange(FoldingRangeParams params) 13 | { 14 | scope document = documents[params.textDocument.uri]; 15 | string file = document.uri.uriToFile; 16 | 17 | if (!backend.hasBest!DCDExtComponent(file)) 18 | return typeof(return).init; 19 | 20 | scope codeText = document.rawText; 21 | 22 | Position cachePos; 23 | size_t cacheBytes; 24 | 25 | auto ret = appender!(FoldingRange[]); 26 | foreach (range; backend.best!DCDExtComponent(file).getFoldingRanges(codeText)) 27 | { 28 | auto start = document.nextPositionBytes(cachePos, cacheBytes, range.start); 29 | auto end = document.nextPositionBytes(cachePos, cacheBytes, range.end); 30 | FoldingRange converted = { 31 | startLine: start.line, 32 | endLine: end.line, 33 | startCharacter: start.character, 34 | endCharacter: end.character, 35 | kind: mapFoldingRangeKind(range.type) 36 | }; 37 | ret ~= converted; 38 | } 39 | return typeof(return)(ret.data); 40 | } 41 | 42 | private FoldingRangeKind mapFoldingRangeKind(FoldingRangeType type) 43 | { 44 | final switch (type) with (FoldingRangeType) 45 | { 46 | case comment: return FoldingRangeKind.comment; 47 | case imports: return FoldingRangeKind.imports; 48 | case region: return FoldingRangeKind.region; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/served/commands/highlight.d: -------------------------------------------------------------------------------- 1 | module served.commands.highlight; 2 | 3 | import std.experimental.logger; 4 | 5 | import served.types; 6 | 7 | import workspaced.api; 8 | import workspaced.com.dcd; 9 | import workspaced.com.dcdext; 10 | 11 | @protocolMethod("textDocument/documentHighlight") 12 | DocumentHighlight[] provideDocumentHighlight(DocumentHighlightParams params) 13 | { 14 | scope document = cast(immutable)documents[params.textDocument.uri].clone(); 15 | auto currOffset = cast(int) document.positionToBytes(params.position); 16 | auto fileConfig = config(document.uri); 17 | 18 | auto result = fileConfig.d.enableDCDHighlight ? documentHighlightImpl(document, currOffset) : null; 19 | 20 | if (!result.length && fileConfig.d.enableFallbackHighlight) 21 | return fallbackDocumentHighlight(document, currOffset); 22 | 23 | return result; 24 | } 25 | 26 | package DocumentHighlight[] documentHighlightImpl(scope ref immutable(Document) document, int currOffset) 27 | { 28 | string file = document.uri.uriToFile; 29 | string codeText = document.rawText; 30 | 31 | if (!backend.hasBest!DCDComponent(file)) 32 | return null; 33 | 34 | DocumentHighlight[] result; 35 | 36 | Position cachePos; 37 | size_t cacheBytes; 38 | 39 | auto localUse = backend.best!DCDComponent(file).findLocalUse(codeText, currOffset).getYield; 40 | trace("localUse: ", localUse); 41 | if (localUse.declarationFilePath == "stdin") 42 | { 43 | auto range = document.wordRangeAt(localUse.declarationLocation); 44 | auto start = document.nextPositionBytes(cachePos, cacheBytes, range[0]); 45 | auto end = document.nextPositionBytes(cachePos, cacheBytes, range[1]); 46 | result ~= DocumentHighlight(TextRange(start, end), DocumentHighlightKind.write.opt); 47 | } 48 | 49 | foreach (use; localUse.uses) 50 | { 51 | auto range = document.wordRangeAt(use); 52 | auto start = document.nextPositionBytes(cachePos, cacheBytes, range[0]); 53 | auto end = document.nextPositionBytes(cachePos, cacheBytes, range[1]); 54 | result ~= DocumentHighlight(TextRange(start, end), DocumentHighlightKind.read.opt); 55 | } 56 | 57 | return result; 58 | } 59 | 60 | package DocumentHighlight[] fallbackDocumentHighlight(scope ref immutable(Document) document, int currOffset) 61 | { 62 | string file = document.uri.uriToFile; 63 | if (!backend.hasBest!DCDExtComponent(file)) 64 | return null; 65 | 66 | DocumentHighlight[] result; 67 | string codeText = document.rawText; 68 | 69 | Position cachePos; 70 | size_t cacheBytes; 71 | 72 | foreach (related; backend.best!DCDExtComponent(file).highlightRelated(codeText, currOffset)) 73 | { 74 | auto start = document.nextPositionBytes(cachePos, cacheBytes, related.range[0]); 75 | auto end = document.nextPositionBytes(cachePos, cacheBytes, related.range[1]); 76 | result ~= DocumentHighlight(TextRange(start, end), DocumentHighlightKind.text.opt); 77 | } 78 | 79 | return result; 80 | } 81 | -------------------------------------------------------------------------------- /source/served/commands/index.d: -------------------------------------------------------------------------------- 1 | module served.commands.index; 2 | 3 | import std.datetime; 4 | import std.experimental.logger; 5 | 6 | import workspaced.api; 7 | import workspaced.coms; 8 | 9 | import served.lsp.protocol; 10 | import served.lsp.protoext; 11 | import served.lsp.textdocumentmanager; 12 | import served.lsp.uri; 13 | import served.types; 14 | import served.utils.async; 15 | 16 | void backgroundIndex() 17 | { 18 | setImmediate({ 19 | reindexAll(); 20 | }); 21 | } 22 | 23 | @protocolNotification("served/reindexAll") 24 | void reindexAll() 25 | { 26 | foreach (workspace; workspaces) 27 | { 28 | if (!workspace.config.d.enableIndex) 29 | continue; 30 | auto stdlib = workspace.stdlibPath; 31 | auto folderPath = workspace.folder.uri.uriToFile; 32 | if (backend.has!IndexComponent(folderPath)) 33 | { 34 | auto indexer = backend.get!IndexComponent(folderPath); 35 | indexer.autoIndexSources(stdlib, true).getYield(); 36 | } 37 | } 38 | 39 | auto now = Clock.currTime; 40 | 41 | foreach (ref doc; documents.documentStore) 42 | { 43 | if (doc.getLanguageId != "d") 44 | continue; 45 | if (!workspace(doc.uri, false).config.d.enableIndex) 46 | continue; 47 | 48 | auto filePath = doc.uri.uriToFile; 49 | if (backend.hasBest!IndexComponent(filePath)) 50 | { 51 | auto indexer = backend.best!IndexComponent(filePath); 52 | indexer.reindex(filePath, now, doc.rawText.length, doc.rawText, true).getYield(); 53 | } 54 | } 55 | 56 | foreach (workspace; workspaces) 57 | { 58 | if (!workspace.config.d.enableIndex) 59 | continue; 60 | auto folderPath = workspace.folder.uri.uriToFile; 61 | if (backend.has!IndexComponent(folderPath)) 62 | { 63 | auto indexer = backend.get!IndexComponent(folderPath); 64 | traceIndexerStats(indexer); 65 | 66 | delayedSaveIndex(indexer); 67 | } 68 | } 69 | } 70 | 71 | int reindexChangeTimeout; 72 | @protocolNotification("textDocument/didChange") 73 | void reindexOnChange(DidChangeTextDocumentParams params) 74 | { 75 | auto document = documents[params.textDocument.uri]; 76 | if (document.getLanguageId != "d") 77 | return; 78 | if (!workspace(params.textDocument.uri).config.d.enableIndex) 79 | return; 80 | 81 | int delay = document.length > 50 * 1024 ? 500 : 50; // be slower after 50KiB 82 | clearTimeout(reindexChangeTimeout); 83 | reindexChangeTimeout = setTimeout({ 84 | auto filePath = params.textDocument.uri.uriToFile; 85 | if (backend.hasBest!IndexComponent(filePath)) 86 | { 87 | auto now = Clock.currTime; 88 | document = documents[params.textDocument.uri]; 89 | auto indexer = backend.best!IndexComponent(filePath); 90 | indexer.reindex(filePath, now, document.rawText.length, document.rawText, false).getYield(); 91 | delayedSaveIndex(indexer); 92 | } 93 | }, delay); 94 | } 95 | 96 | @protocolNotification("textDocument/didSave") 97 | void reindexOnSave(DidSaveTextDocumentParams params) 98 | { 99 | auto document = documents[params.textDocument.uri]; 100 | if (document.getLanguageId != "d") 101 | return; 102 | if (!workspace(params.textDocument.uri).config.d.enableIndex) 103 | return; 104 | 105 | auto filePath = params.textDocument.uri.uriToFile; 106 | clearTimeout(reindexChangeTimeout); 107 | 108 | if (backend.hasBest!IndexComponent(filePath)) 109 | { 110 | auto indexer = backend.best!IndexComponent(filePath); 111 | indexer.reindexSaved(filePath, document.rawText).getYield(); 112 | delayedSaveIndex(indexer); 113 | } 114 | } 115 | 116 | int reindexSaveTimeout; 117 | private void delayedSaveIndex(IndexComponent index) 118 | { 119 | clearTimeout(reindexSaveTimeout); 120 | reindexSaveTimeout = setTimeout({ 121 | index.saveIndex(); 122 | }, 2_500); 123 | } 124 | 125 | private void traceIndexerStats(IndexComponent index) 126 | { 127 | import core.memory; 128 | GC.collect(); 129 | GC.minimize(); 130 | 131 | trace("Indexer stats for ", index.refInstance.cwd, ":"); 132 | auto stats = index.getHealth(); 133 | trace("- failed files: ", stats.failedFiles); 134 | trace("- total modules: ", stats.indexedModules); 135 | trace("- total definitions: ", stats.numDefinitions); 136 | trace("- total imports: ", stats.numImports); 137 | } 138 | -------------------------------------------------------------------------------- /source/served/commands/references.d: -------------------------------------------------------------------------------- 1 | module served.commands.references; 2 | 3 | import core.sync.mutex; 4 | import core.thread; 5 | 6 | import served.types; 7 | import served.utils.async; 8 | 9 | import workspaced.com.dcd; 10 | import workspaced.com.index; 11 | import workspaced.com.references; 12 | 13 | @protocolMethod("textDocument/references") 14 | AsyncReceiver!Location findReferences(ReferenceParams params) 15 | { 16 | auto receiver = new AsyncReceiver!Location(); 17 | scope document = documents[params.textDocument.uri]; 18 | auto offset = cast(int) document.positionToBytes(params.position); 19 | string file = document.uri.uriToFile; 20 | scope codeText = document.rawText; 21 | 22 | if (!backend.hasBest!DCDComponent(file)) 23 | { 24 | receiver.end(); 25 | return receiver; 26 | } 27 | 28 | bool includeDecl = params.context.includeDeclaration; 29 | setImmediate({ 30 | scope (exit) 31 | receiver.end(); 32 | 33 | try 34 | { 35 | backend.best!ReferencesComponent(file) 36 | .findReferences(file, codeText, offset, 37 | (refs) { 38 | Location[] ret; 39 | foreach (r; refs.references) 40 | { 41 | if (!includeDecl && refs.definitionFile == r.file && refs.definitionLocation == r.location) 42 | continue; 43 | resolveLocation(ret, r.file, r.location); 44 | } 45 | receiver.put(ret); 46 | }) 47 | .getYield(); 48 | } 49 | catch (Exception e) 50 | { 51 | receiver.error = e; 52 | } 53 | }); 54 | return receiver; 55 | } 56 | 57 | private void resolveLocation(ref Location[] ret, string file, int location) 58 | { 59 | auto uri = file.uriFromFile; 60 | scope doc = documents.getOrFromFilesystem(uri); 61 | auto pos = doc.bytesToPosition(location); 62 | ret ~= Location(uri, doc.wordRangeAt(pos)); 63 | } 64 | 65 | class AsyncReceiver(T) 66 | { 67 | bool notified; 68 | Mutex m; 69 | Exception error; 70 | T[] queued; 71 | T[] current; 72 | 73 | this() 74 | { 75 | m = new Mutex(); 76 | } 77 | 78 | bool ended; 79 | 80 | T[] front() 81 | { 82 | if (error) 83 | throw error; 84 | return current; 85 | } 86 | 87 | void popFront() 88 | { 89 | if (error) 90 | throw error; 91 | if (ended) 92 | { 93 | current = queued; 94 | queued = null; 95 | return; 96 | } 97 | 98 | wait(); 99 | 100 | synchronized (m) 101 | { 102 | notified = false; 103 | current = queued; 104 | queued = null; 105 | } 106 | } 107 | 108 | bool empty() 109 | { 110 | if (error) 111 | throw error; 112 | return ended && current.length == 0; 113 | } 114 | 115 | void put(T[] data) 116 | { 117 | if (!data.length) 118 | return; 119 | 120 | synchronized (m) 121 | { 122 | queued ~= data; 123 | notified = true; 124 | } 125 | } 126 | 127 | void wait() 128 | { 129 | while (!notified) 130 | Fiber.yield(); 131 | } 132 | 133 | void end() 134 | { 135 | ended = true; 136 | synchronized (m) 137 | { 138 | notified = true; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /source/served/commands/rename.d: -------------------------------------------------------------------------------- 1 | module served.commands.rename; 2 | 3 | import std.algorithm; 4 | import std.experimental.logger; 5 | 6 | import served.types; 7 | import served.commands.highlight; 8 | 9 | import workspaced.api; 10 | import workspaced.com.dcd; 11 | 12 | @protocolMethod("textDocument/rename") 13 | Nullable!WorkspaceEdit provideRename(RenameParams params) 14 | { 15 | scope document = cast(immutable)documents[params.textDocument.uri].clone(); 16 | auto currOffset = cast(int) document.positionToBytes(params.position); 17 | 18 | auto highlight = documentHighlightImpl(document, currOffset); 19 | if (highlight.length && highlight[0].kind == DocumentHighlightKind.write) 20 | { 21 | TextEdit[] edits; 22 | foreach (i, h; highlight) 23 | if (i == 0 || h.range != edits[0].range) 24 | edits ~= TextEdit(h.range, params.newName); 25 | WorkspaceEdit edit; 26 | edits.sort!"a.range.start>b.range.start"; 27 | edit.changes[params.textDocument.uri] = edits; 28 | return typeof(return)(edit); 29 | } 30 | 31 | return typeof(return).init; 32 | } 33 | 34 | @protocolMethod("textDocument/prepareRename") 35 | Nullable!TextRange prepareRename(PrepareRenameParams params) 36 | { 37 | scope document = cast(immutable)documents[params.textDocument.uri].clone(); 38 | auto currOffset = cast(int) document.positionToBytes(params.position); 39 | 40 | auto highlight = documentHighlightImpl(document, currOffset); 41 | if (highlight.length && highlight[0].kind == DocumentHighlightKind.write) 42 | return typeof(return)(highlight[0].range); 43 | 44 | return typeof(return).init; 45 | } 46 | -------------------------------------------------------------------------------- /source/served/commands/test_provider.d: -------------------------------------------------------------------------------- 1 | module served.commands.test_provider; 2 | 3 | import served.types; 4 | import served.commands.symbol_search; 5 | import served.utils.async : spawnFiber; 6 | 7 | import workspaced.api; 8 | import workspaced.coms; 9 | 10 | import std.algorithm; 11 | import std.experimental.logger; 12 | import std.file : FileException; 13 | import std.path : baseName; 14 | import std.range; 15 | 16 | /// Set to true by app.d if `--provide test-runner` is given 17 | /// Makes serve-d emit unittests on every save 18 | __gshared bool doTrackTests = false; 19 | 20 | UnittestProject[DocumentUri] projectTests; 21 | 22 | @onProjectAvailable 23 | void onPreviewProjectForUnittest(WorkspaceD.Instance instance, string dir, string rootFolderUri) 24 | { 25 | if (!doTrackTests) 26 | return; 27 | 28 | string rootUri = instance.cwd.uriFromFile; 29 | auto project = &projectTests.require(rootUri, UnittestProject(rootUri, null, null, true)); 30 | 31 | rpc.notifyMethod("coded/pushProjectTests", *project); 32 | } 33 | 34 | @onAddedProject 35 | void onDidAddProjectForUnittest(WorkspaceD.Instance instance, string dir, string rootFolderUri) 36 | { 37 | if (!doTrackTests) 38 | return; 39 | 40 | spawnFiber("Scan DUB project for unittests", { 41 | if (!instance.has!DubComponent) 42 | return; 43 | 44 | rescanProject(instance); 45 | }, requiredLibdparsePageCount * 2); 46 | } 47 | 48 | @protocolNotification("textDocument/didSave") 49 | void onDidSaveCheckForUnittest(DidSaveTextDocumentParams params) 50 | { 51 | if (!doTrackTests) 52 | return; 53 | 54 | if (!params.textDocument.uri.endsWith(".d")) 55 | return; 56 | 57 | auto instance = backend.getBestInstance!DubComponent(params.textDocument.uri); 58 | if (!instance) 59 | return; 60 | 61 | spawnFiber("Run didSave processors", { 62 | auto projectUri = workspace(params.textDocument.uri).folder.uri; 63 | 64 | auto project = &projectTests.require(projectUri, UnittestProject(projectUri)); 65 | rescanFile(*project, params.textDocument); 66 | 67 | rpc.notifyMethod("coded/pushProjectTests", *project); 68 | }, requiredLibdparsePageCount * 2); 69 | } 70 | 71 | @protocolNotification("served/rescanTests") 72 | void rescanTests(RescanTestsParams params) 73 | { 74 | string cwd = params.uri.length ? uriToFile(params.uri) : null; 75 | foreach (instance; backend.instances) 76 | { 77 | if (!cwd.length || instance.cwd == cwd) 78 | { 79 | if (auto lazyInstance = cast(LazyWorkspaceD.LazyInstance)instance) 80 | { 81 | if (cwd.length || lazyInstance.didCallAccess) 82 | { 83 | rescanProject(instance); 84 | } 85 | } 86 | else 87 | { 88 | rescanProject(instance); 89 | } 90 | } 91 | } 92 | } 93 | 94 | private void rescanFile(ref UnittestProject project, TextDocumentIdentifier documentIdentifier) 95 | { 96 | auto document = documents[documentIdentifier.uri]; 97 | auto symbols = provideDocumentSymbolsOld(DocumentSymbolParamsEx(documentIdentifier, true)); 98 | 99 | UnittestInfo[] tests; 100 | 101 | foreach (test; symbols) 102 | { 103 | if (test.extendedType != SymbolKindEx.test) 104 | continue; 105 | 106 | string name = test.name; 107 | if (test.detail.length) 108 | name = test.detail; 109 | 110 | tests ~= UnittestInfo(test.name, name, test.containerName, test.location.range); 111 | } 112 | 113 | string modulename = backend.get!ModulemanComponent.moduleName(document.rawText); 114 | if (!modulename.length) 115 | modulename = "(file) " ~ documentIdentifier.uri.uriToFile.baseName; 116 | auto entry = UnittestModule(modulename, documentIdentifier.uri, tests); 117 | 118 | auto trisect = project.modules 119 | .assumeSorted!("a.moduleName < b.moduleName") 120 | .trisect(entry); 121 | 122 | if (trisect[1].length) 123 | { 124 | if (tests.length == 0) // remove 125 | project.modules = project.modules.remove(tests.length); 126 | else 127 | project.modules[trisect[0].length] = entry; 128 | } 129 | else if (tests.length) 130 | { 131 | project.modules = project.modules[0 .. trisect[0].length] 132 | ~ entry 133 | ~ project.modules[trisect[0].length .. $]; 134 | } 135 | } 136 | 137 | private void rescanProject(WorkspaceD.Instance instance) 138 | { 139 | import std.datetime.stopwatch : StopWatch; 140 | import std.path : buildNormalizedPath; 141 | 142 | DubComponent dub = instance.get!DubComponent; 143 | auto settings = dub.rootPackageBuildSettings(); 144 | 145 | string rootUri = instance.cwd.uriFromFile; 146 | 147 | auto project = &projectTests.require(rootUri, UnittestProject(rootUri)); 148 | 149 | StopWatch sw; 150 | sw.start(); 151 | 152 | project.name = settings.packageName; 153 | project.needsLoad = false; 154 | project.modules = null; 155 | foreach (path; settings.sourceFiles) 156 | { 157 | auto fullPath = buildNormalizedPath(settings.packagePath, path); 158 | if (!fullPath.endsWith(".d", ".dpp", ".di")) 159 | continue; 160 | 161 | auto uri = fullPath.uriFromFile; 162 | 163 | bool tempLoad; 164 | try 165 | documents.getOrFromFilesystem(uri, tempLoad); 166 | catch (FileException e) 167 | { 168 | warningf("Failed to read file %s for tests: %s", fullPath, e); 169 | continue; 170 | } 171 | 172 | scope (exit) 173 | if (tempLoad) 174 | documents.unloadDocument(uri); 175 | 176 | try 177 | { 178 | rescanFile(*project, TextDocumentIdentifier(uri)); 179 | } 180 | catch (Exception e) 181 | { 182 | warningf("Failed to analyze file %s for tests: %s", fullPath, e); 183 | } 184 | } 185 | 186 | rpc.notifyMethod("coded/pushProjectTests", *project); 187 | 188 | sw.stop(); 189 | infof("Found %s modules with tests in %s (%s) in %s", 190 | project.modules.length, 191 | project.name, 192 | project.workspaceUri, 193 | sw.peek); 194 | } 195 | 196 | -------------------------------------------------------------------------------- /source/served/info.d: -------------------------------------------------------------------------------- 1 | module served.info; 2 | 3 | static immutable Version = [0, 8, 0]; 4 | static immutable VersionSuffix = "beta.18"; // like beta.1 5 | static immutable string BundledDependencies = "dub, dfmt and dscanner are bundled within (compiled in)"; 6 | 7 | version (Windows) version (DigitalMars) static assert(false, 8 | "DMD not supported on Windows. Please use LDC."); 9 | -------------------------------------------------------------------------------- /source/served/io/git_build.d: -------------------------------------------------------------------------------- 1 | module served.io.git_build; 2 | 3 | import served.types; 4 | 5 | import std.conv : to; 6 | import std.path : buildPath; 7 | import std.string : join; 8 | 9 | import rm.rf; 10 | 11 | import fs = std.file; 12 | 13 | bool compileDependency(string cwd, string name, string gitURI, string[][] commands) 14 | { 15 | import std.process : pipe, spawnProcess, Config, tryWait, wait; 16 | 17 | int run(string[] cmd, string cwd) 18 | { 19 | import core.thread : Thread, Fiber; 20 | 21 | rpc.notifyMethod("coded/logInstall", "> " ~ cmd.join(" ")); 22 | auto stdin = pipe(); 23 | auto stdout = pipe(); 24 | auto pid = spawnProcess(cmd, stdin.readEnd, stdout.writeEnd, 25 | stdout.writeEnd, null, Config.none, cwd); 26 | stdin.writeEnd.close(); 27 | size_t i; 28 | string[] lines; 29 | bool done; 30 | new Thread({ 31 | scope (exit) 32 | done = true; 33 | foreach (line; stdout.readEnd.byLine) 34 | lines ~= line.idup; 35 | }).start(); 36 | while (!pid.tryWait().terminated || !done || i < lines.length) 37 | { 38 | if (i < lines.length) 39 | { 40 | rpc.notifyMethod("coded/logInstall", lines[i++]); 41 | } 42 | Fiber.yield(); 43 | } 44 | return pid.wait; 45 | } 46 | 47 | rpc.notifyMethod("coded/logInstall", "Installing into " ~ cwd); 48 | try 49 | { 50 | auto newCwd = buildPath(cwd, name); 51 | if (fs.exists(newCwd)) 52 | { 53 | rpc.notifyMethod("coded/logInstall", "Deleting old installation from " ~ newCwd); 54 | try 55 | { 56 | rmdirRecurseForce(newCwd); 57 | } 58 | catch (Exception) 59 | { 60 | rpc.notifyMethod("coded/logInstall", "WARNING: Failed to delete " ~ newCwd); 61 | } 62 | } 63 | auto ret = run([ 64 | anyConfig.git.userPath, "clone", "--recursive", "--depth=1", gitURI, 65 | name 66 | ], cwd); 67 | if (ret != 0) 68 | throw new Exception("git ended with error code " ~ ret.to!string); 69 | foreach (command; commands) 70 | run(command, newCwd); 71 | return true; 72 | } 73 | catch (Exception e) 74 | { 75 | rpc.notifyMethod("coded/logInstall", "Failed to install " ~ name); 76 | rpc.notifyMethod("coded/logInstall", e.toString); 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/served/io/http_wrap.d: -------------------------------------------------------------------------------- 1 | module served.io.http_wrap; 2 | 3 | import served.http; 4 | import served.types; 5 | 6 | import std.json : JSONType; 7 | 8 | __gshared bool letEditorDownload; 9 | 10 | struct InteractiveDownload 11 | { 12 | string url; 13 | string title; 14 | string output; 15 | } 16 | 17 | void downloadFile(string url, string title, string into) 18 | { 19 | if (letEditorDownload) 20 | { 21 | if (rpc.sendRequest("coded/interactiveDownload", InteractiveDownload(url, 22 | title, into)).resultJson != "true") 23 | throw new Exception("The download has failed."); 24 | } 25 | else 26 | { 27 | downloadFileManual(url, title, into, (msg) { 28 | rpc.notifyMethod("coded/logInstall", msg); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /source/served/io/nothrow_fs.d: -------------------------------------------------------------------------------- 1 | module served.io.nothrow_fs; 2 | 3 | public import fs = std.file; 4 | 5 | import std.experimental.logger; 6 | import std.utf; 7 | 8 | auto tryDirEntries(string path, fs.SpanMode mode, bool followSymlink = true) 9 | { 10 | try 11 | { 12 | return nothrowDirIterator(fs.dirEntries(path, mode, followSymlink)); 13 | } 14 | catch (fs.FileException) 15 | { 16 | return typeof(return).init; 17 | } 18 | } 19 | 20 | auto tryDirEntries(string path, string pattern, fs.SpanMode mode, bool followSymlink = true) 21 | { 22 | try 23 | { 24 | return nothrowDirIterator(fs.dirEntries(path, pattern, mode, followSymlink)); 25 | } 26 | catch (fs.FileException) 27 | { 28 | return typeof(return).init; 29 | } 30 | } 31 | 32 | private NothrowDirIterator!T nothrowDirIterator(T)(T range) 33 | { 34 | return NothrowDirIterator!T(range); 35 | } 36 | 37 | private struct NothrowDirIterator(T) 38 | { 39 | @safe: 40 | T base; 41 | bool crashed; 42 | 43 | public: 44 | @property bool empty() 45 | { 46 | try 47 | { 48 | return crashed || base.empty; 49 | } 50 | catch (fs.FileException) 51 | { 52 | return crashed = true; 53 | } 54 | catch (UTFException e) 55 | { 56 | (() @trusted => error("Got malformed UTF string in dirIterator, something has probably corrupted. ", e))(); 57 | return crashed = true; 58 | } 59 | } 60 | 61 | @property auto front() 62 | { 63 | try 64 | { 65 | return base.front; 66 | } 67 | catch (fs.FileException) 68 | { 69 | crashed = true; 70 | } 71 | catch (UTFException e) 72 | { 73 | (() @trusted => error("Got malformed UTF string in dirIterator, something has probably corrupted. ", e))(); 74 | crashed = true; 75 | } 76 | return fs.DirEntry.init; 77 | } 78 | 79 | void popFront() 80 | { 81 | try 82 | { 83 | base.popFront(); 84 | } 85 | catch (fs.FileException) 86 | { 87 | crashed = true; 88 | } 89 | catch (UTFException e) 90 | { 91 | (() @trusted => error("Got malformed UTF string in dirIterator, something has probably corrupted. ", e))(); 92 | crashed = true; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /source/served/linters/dfmt.d: -------------------------------------------------------------------------------- 1 | module served.linters.dfmt; 2 | 3 | import std.algorithm; 4 | import std.array; 5 | import std.conv; 6 | import std.file; 7 | import std.json; 8 | import std.path; 9 | import std.string; 10 | 11 | import served.extension; 12 | import served.linters.diagnosticmanager; 13 | import served.types; 14 | 15 | import workspaced.api; 16 | import workspaced.coms; 17 | 18 | import workspaced.com.dfmt; 19 | 20 | static immutable string DfmtDiagnosticSource = "dfmt"; 21 | 22 | enum DiagnosticSlot = 2; 23 | 24 | void lint(Document document) 25 | { 26 | auto fileConfig = config(document.uri); 27 | if (!fileConfig.d.enableFormatting) 28 | return; 29 | 30 | if (!backend.has!DfmtComponent) 31 | return; 32 | auto dfmt = backend.get!DfmtComponent; 33 | 34 | createDiagnosticsFor!DiagnosticSlot(document.uri) = lintDfmt(dfmt, document); 35 | updateDiagnostics(document.uri); 36 | } 37 | 38 | private Diagnostic[] lintDfmt(DfmtComponent dfmt, ref Document document) 39 | { 40 | auto instructions = dfmt.findDfmtInstructions(document.rawText); 41 | 42 | auto diagnostics = appender!(Diagnostic[]); 43 | bool fmtOn = true; 44 | 45 | Position positionCache; 46 | size_t byteCache; 47 | 48 | void setFmt(DfmtInstruction instruction, bool on) 49 | { 50 | if (fmtOn == on) 51 | { 52 | } 53 | fmtOn = on; 54 | } 55 | 56 | foreach (DfmtInstruction instruction; instructions) 57 | { 58 | Diagnostic d; 59 | d.source = DfmtDiagnosticSource; 60 | auto start = document.movePositionBytes(positionCache, byteCache, instruction.index); 61 | auto end = document.movePositionBytes(start, instruction.index, instruction.index + instruction.length); 62 | positionCache = end; 63 | byteCache = instruction.index + instruction.length; 64 | d.range = TextRange(start, end); 65 | 66 | final switch (instruction.type) 67 | { 68 | case DfmtInstruction.Type.dfmtOn: 69 | case DfmtInstruction.Type.dfmtOff: 70 | bool on = instruction.type == DfmtInstruction.Type.dfmtOn; 71 | if (on == fmtOn) 72 | { 73 | d.message = on ? "Redundant `dfmt on`" : "Redundant `dfmt off`"; 74 | d.code = JsonValue(on ? "redundant-on" : "redundant-off"); 75 | d.severity = DiagnosticSeverity.hint; 76 | d.tags = [DiagnosticTag.unnecessary]; 77 | diagnostics ~= d; 78 | } 79 | fmtOn = on; 80 | break; 81 | case DfmtInstruction.Type.unknown: 82 | d.message = "Not a valid dfmt command (try `//dfmt off` or `//dfmt on` instead)"; 83 | d.code = JsonValue("unknown-comment"); 84 | d.severity = DiagnosticSeverity.warning; 85 | diagnostics ~= d; 86 | break; 87 | } 88 | } 89 | 90 | return diagnostics.data; 91 | } 92 | 93 | void clear() 94 | { 95 | diagnostics[DiagnosticSlot] = null; 96 | updateDiagnostics(); 97 | } 98 | 99 | @("misspelling on/off") 100 | unittest 101 | { 102 | auto dfmt = new DfmtComponent(); 103 | dfmt.workspaced = new WorkspaceD(); 104 | Document d = Document.nullDocument(`void foo() { 105 | //dfmt offs 106 | int i = 5; 107 | //dfmt onf 108 | }`); 109 | auto linted = lintDfmt(dfmt, d); 110 | assert(linted.length == 2); 111 | assert(linted[0].severity.deref == DiagnosticSeverity.warning); 112 | assert(linted[1].severity.deref == DiagnosticSeverity.warning); 113 | } 114 | 115 | @("redundant on/off") 116 | unittest 117 | { 118 | auto dfmt = new DfmtComponent(); 119 | dfmt.workspaced = new WorkspaceD(); 120 | Document d = Document.nullDocument(`void foo() { 121 | //dfmt on 122 | //dfmt off 123 | int i = 5; 124 | //dfmt off 125 | //dfmt ons 126 | }`); 127 | auto linted = lintDfmt(dfmt, d); 128 | import std.stdio; stderr.writeln("diagnostics:\n", linted); 129 | assert(linted.length == 3); 130 | assert(linted[0].severity.deref == DiagnosticSeverity.hint); 131 | assert(linted[1].severity.deref == DiagnosticSeverity.hint); 132 | assert(linted[2].severity.deref == DiagnosticSeverity.warning); 133 | } 134 | -------------------------------------------------------------------------------- /source/served/linters/diagnosticmanager.d: -------------------------------------------------------------------------------- 1 | module served.linters.diagnosticmanager; 2 | 3 | import std.array : array; 4 | import std.algorithm : map, sort; 5 | 6 | import served.utils.memory; 7 | import served.types; 8 | 9 | enum NumDiagnosticProviders = 3; 10 | alias DiagnosticCollection = PublishDiagnosticsParams[]; 11 | DiagnosticCollection[NumDiagnosticProviders] diagnostics; 12 | 13 | DiagnosticCollection combinedDiagnostics; 14 | DocumentUri[] publishedUris; 15 | 16 | void combineDiagnostics() 17 | { 18 | combinedDiagnostics.length = 0; 19 | foreach (provider; diagnostics) 20 | { 21 | foreach (errors; provider) 22 | { 23 | size_t index = combinedDiagnostics.length; 24 | foreach (i, existing; combinedDiagnostics) 25 | { 26 | if (existing.uri == errors.uri) 27 | { 28 | index = i; 29 | break; 30 | } 31 | } 32 | if (index == combinedDiagnostics.length) 33 | combinedDiagnostics ~= PublishDiagnosticsParams(errors.uri); 34 | combinedDiagnostics[index].diagnostics ~= errors.diagnostics; 35 | } 36 | } 37 | } 38 | 39 | /// Returns a reference to existing diagnostics for a given url in a given slot or creates a new array for them and returns the reference for it. 40 | /// Params: 41 | /// slot = the diagnostic provider slot to edit 42 | /// uri = the document uri to attach the diagnostics array for 43 | ref auto createDiagnosticsFor(int slot)(string uri) 44 | { 45 | static assert(slot < NumDiagnosticProviders); 46 | foreach (ref existing; diagnostics[slot]) 47 | if (existing.uri == uri) 48 | return existing.diagnostics; 49 | 50 | return pushRef(diagnostics[slot], PublishDiagnosticsParams(uri, null)).diagnostics; 51 | } 52 | 53 | private ref T pushRef(T)(ref T[] arr, T value) 54 | { 55 | auto len = arr.length++; 56 | return arr[len] = value; 57 | } 58 | 59 | void updateDiagnostics(string uriHint = "") 60 | { 61 | combineDiagnostics(); 62 | foreach (diagnostics; combinedDiagnostics) 63 | { 64 | if (!uriHint.length || diagnostics.uri == uriHint) 65 | { 66 | // TODO: related information 67 | RequestMessageRaw request; 68 | request.method = "textDocument/publishDiagnostics"; 69 | request.paramsJson = diagnostics.serializeJson; 70 | rpc.send(request); 71 | } 72 | } 73 | 74 | // clear old diagnostics 75 | auto diags = combinedDiagnostics.map!"a.uri".array; 76 | auto sorted = diags.sort!"a 0) 67 | { 68 | int i = 0; 69 | if (auto node = cast(TagNode) completion.parser.root.children[i]) 70 | { 71 | if (node.name == "extends" && completion.parser.root.children.length > 1) 72 | i++; 73 | } 74 | 75 | if (auto comment = cast(HiddenComment) completion.parser.root.children[i]) 76 | { 77 | string startComment = comment.content.strip; 78 | info("Have context ", startComment); 79 | if (startComment.startsWith("context=") && extractContext) 80 | { 81 | auto context = startComment["context=".length .. $]; 82 | auto end = context.indexOfAny(" ;,"); 83 | if (end != -1) 84 | context = context[0 .. end]; 85 | 86 | context = context.strip; 87 | if (!context.endsWith(".d")) 88 | context ~= ".d"; 89 | 90 | auto currentFile = completion.parser.input.file.baseName; 91 | 92 | foreach (doc; documents.documentStore) 93 | { 94 | if (doc.uri.endsWith(context)) 95 | { 96 | auto content = doc.rawText; 97 | infof("Searching for diet file '%s' in context file '%s'", currentFile, doc.uri); 98 | 99 | auto index = searchDietTemplateUsage(content, currentFile); 100 | if (index >= 0) 101 | prefix = content[0 .. index].idup; 102 | else 103 | info("Failed finding diet file, error ", index); 104 | break; 105 | } 106 | } 107 | 108 | if (!prefix.length) 109 | { 110 | infof("Didn't find any valid context for diet file '%s' when searching for context '%s'", 111 | currentFile, context); 112 | } 113 | } 114 | } 115 | } 116 | 117 | extractD(completion, offset, code, dOffset, prefix); 118 | } 119 | 120 | ptrdiff_t searchDietTemplateUsage(scope const(char)[] code, scope const(char)[] dietFile) 121 | { 122 | auto index = code.indexOf(dietFile); 123 | if (index == -1) 124 | return -2; 125 | 126 | if (!code[index + dietFile.length .. $].startsWith("\"", "`")) 127 | return -3; 128 | 129 | auto funcStart = code[0 .. index].lastIndexOfAny(";!("); 130 | if (funcStart == -1) 131 | return -4; 132 | 133 | return code[0 .. funcStart].lastIndexOfAny(";\r\n"); 134 | } 135 | -------------------------------------------------------------------------------- /source/served/utils/progress.d: -------------------------------------------------------------------------------- 1 | module served.utils.progress; 2 | 3 | import std.format; 4 | import core.time : MonoTime; 5 | 6 | import served.types; 7 | 8 | /// Documents a progress type should have progress attached 9 | private enum withProgress; 10 | 11 | /// Documents a progress type can possibly have some progress attached 12 | private enum optionalProgress; 13 | 14 | /// The types to report 15 | enum ProgressType 16 | { 17 | /// default invalid/ignored value 18 | unknown, 19 | /// sent when serve-d is first registering all workspace-d components. Sent on first configLoad. 20 | globalStartup, 21 | /// sent before each workspace that is going to be loaded. Sent for every workspace. 22 | /// sent with workspaceUri argument 23 | @optionalProgress configLoad, 24 | /// sent when all workspaces have been loaded. Sent when everything is initialized. 25 | configFinish, 26 | /// sent for each root of a workspace on startup. Sent for every configLoad for all roots. 27 | /// sent with root.uri argument 28 | /// 29 | /// With current lazy implementation this is instantly done 30 | @withProgress workspaceStartup, 31 | /// sent for every auto completion server starting up. Sent after all workspaceStartups for a workspace. 32 | /// sent with root.uri argument 33 | /// 34 | /// With current lazy implementation this is instantly done 35 | @withProgress completionStartup, 36 | /// sent when dub is being reloaded 37 | /// sent with instance.uri argument 38 | @withProgress dubReload, 39 | /// sent when the import paths are being indexed 40 | /// sent with instance.uri argument 41 | @withProgress importReload, 42 | /// sent when dub is being upgraded before imports are being reloading 43 | /// sent with instance.uri argument 44 | @withProgress importUpgrades, 45 | } 46 | 47 | void reportProgress(Args...)(bool condition, ProgressType type, size_t step, size_t max, Args args) 48 | { 49 | if (condition) 50 | reportProgress(type, step, max, args); 51 | } 52 | 53 | void reportProgress(Args...)(ProgressType type, size_t step, size_t max, Args args) 54 | { 55 | double time = (MonoTime.currTime() - startupTime).total!"msecs" / 1000.0; 56 | if (max > 0) 57 | rpc.log(format!"[progress] [%09.3f] [%s] %d / %d: "(time, type, step, max), args); 58 | else if (step > 0) 59 | rpc.log(format!"[progress] [%09.3f] [%s] %d: "(time, type, step), args); 60 | else if (Args.length > 0) 61 | rpc.log(format!"[progress] [%09.3f] [%s]: "(time, type), args); 62 | else 63 | rpc.log(format!"[progress] [%09.3f] [%s]"(time, type)); 64 | } 65 | -------------------------------------------------------------------------------- /source/served/utils/trace.d: -------------------------------------------------------------------------------- 1 | module served.utils.trace; 2 | 3 | import core.time; 4 | import std.stdio : File; 5 | 6 | struct TraceDataStat(T) 7 | { 8 | T min; 9 | T max; 10 | T total; 11 | 12 | void resetTo(T value) 13 | { 14 | min = value; 15 | max = value; 16 | total = value; 17 | } 18 | 19 | void put(T value) 20 | { 21 | if (value < min) 22 | min = value; 23 | if (value > max) 24 | max = value; 25 | total += value; 26 | } 27 | 28 | auto map(alias fn)() 29 | { 30 | import std.functional : unaryFun; 31 | 32 | alias mapFun = unaryFun!fn; 33 | 34 | auto minV = mapFun(min); 35 | auto maxV = mapFun(max); 36 | auto totalV = mapFun(total); 37 | 38 | return TraceDataStat!(typeof(minV))(minV, maxV, totalV); 39 | } 40 | } 41 | 42 | struct TraceState 43 | { 44 | MonoTime time; 45 | long gc; 46 | 47 | static TraceState current() 48 | { 49 | import core.memory : GC; 50 | 51 | TraceState ret; 52 | ret.time = MonoTime.currTime; 53 | static if (__traits(hasMember, typeof(GC.stats()), "allocatedInCurrentThread")) 54 | ret.gc = GC.stats().allocatedInCurrentThread; 55 | else 56 | ret.gc = GC.stats().usedSize; 57 | return ret; 58 | } 59 | } 60 | 61 | struct TraceData 62 | { 63 | TraceDataStat!long gcUsage; 64 | TraceDataStat!Duration runtime; 65 | int timesCalled; 66 | 67 | TraceState start() 68 | { 69 | return TraceState.current(); 70 | } 71 | 72 | void end(TraceState state) 73 | { 74 | auto now = TraceState.current(); 75 | if (timesCalled == 0) 76 | { 77 | gcUsage.resetTo(now.gc - state.gc); 78 | runtime.resetTo(now.time - state.time); 79 | } 80 | else 81 | { 82 | gcUsage.put(now.gc - state.gc); 83 | runtime.put(now.time - state.time); 84 | } 85 | timesCalled++; 86 | } 87 | } 88 | 89 | TraceData*[string] _traceInfos; 90 | 91 | /// CTFE function to generate mixin code for function tracing 92 | /// The trace 93 | const(char)[] traceStatistics(const(char)[] name) 94 | { 95 | import std.ascii : isAlphaNum; 96 | 97 | char[] id; 98 | foreach (char c; name) 99 | if (c.isAlphaNum) 100 | id ~= c; 101 | //dfmt off 102 | return ` 103 | static bool _trace_init_` ~ id ~ ` = false; 104 | static TraceData _trace_info_` ~ id ~ `; 105 | if (!_trace_init_` ~ id ~ `) 106 | { 107 | _traceInfos["` ~ name ~ `"] = &_trace_info_` ~ id ~ `; 108 | _trace_init_` ~ id ~ ` = true; 109 | } 110 | auto _trace_state_` ~ id ~ ` = _trace_info_` ~ id ~ `.start(); 111 | 112 | scope (exit) 113 | { 114 | _trace_info_` ~ id ~ `.end(_trace_state_` ~ id ~ `); 115 | } 116 | `; 117 | //dfmt on 118 | } 119 | 120 | void dumpTraceInfos(File output) 121 | { 122 | import std.array : array; 123 | import std.algorithm : sort; 124 | 125 | output.write("trace name\tnum calls"); 126 | foreach (stat; ["time", "gc"]) 127 | output.writef("\tmin %s\tmax %s\ttotal %s\tavg %s", 128 | stat, stat, stat, stat); 129 | output.writeln(); 130 | 131 | auto kv = _traceInfos.byKeyValue.array 132 | .sort!"a.value.timesCalled > b.value.timesCalled"; 133 | 134 | foreach (entry; kv) 135 | { 136 | int total = entry.value.timesCalled; 137 | double dTotal = cast(double)total; 138 | 139 | void dumpStat(T)(TraceDataStat!T stat) 140 | { 141 | output.write('\t', stat.min); 142 | output.write('\t', stat.max); 143 | output.write('\t', stat.total); 144 | output.write('\t', stat.total / dTotal); 145 | } 146 | 147 | output.write(entry.key, '\t', total); 148 | dumpStat(entry.value.runtime.map!"a.total!`msecs`"); 149 | dumpStat(entry.value.gcUsage); 150 | output.writeln(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /source/served/workers/profilegc.d: -------------------------------------------------------------------------------- 1 | module served.workers.profilegc; 2 | 3 | import std.algorithm; 4 | import std.array; 5 | import std.ascii : isDigit; 6 | import std.conv; 7 | import std.experimental.logger; 8 | import std.format; 9 | import std.string; 10 | 11 | import served.types; 12 | 13 | import workspaced.api; 14 | 15 | struct ProfileGCEntry 16 | { 17 | size_t bytesAllocated; 18 | size_t allocationCount; 19 | string type; 20 | string uri, displayFile; 21 | uint line; 22 | } 23 | 24 | struct ProfileGCCache 25 | { 26 | struct PerDocumentCache 27 | { 28 | ProfileGCEntry[] entries; 29 | 30 | auto process(DocumentUri relativeToUri, scope const(char)[] content) 31 | { 32 | entries = null; 33 | foreach (line; content.lineSplitter) 34 | { 35 | auto cols = line.split; 36 | if (cols.length < 5) 37 | continue; 38 | auto typeStart = cols[2].ptr - line.ptr; 39 | if (typeStart < 0 || typeStart > line.length) 40 | typeStart = 0; 41 | auto fileStart = line.lastIndexOfAny(" \t"); 42 | if (fileStart != -1) 43 | { 44 | fileStart++; 45 | auto colon = line.indexOf(":", fileStart); 46 | if (colon != -1 && line[colon + 1 .. $].strip.all!isDigit) 47 | { 48 | auto file = line[fileStart .. colon]; 49 | auto lineNo = line[colon + 1 .. $].strip.to!uint; 50 | entries.assumeSafeAppend ~= ProfileGCEntry( 51 | cols[0].to!size_t, 52 | cols[1].to!size_t, 53 | line[typeStart .. fileStart - 1].strip.idup, 54 | uriBuildNormalized(relativeToUri, file), 55 | file.idup, 56 | lineNo 57 | ); 58 | } 59 | } 60 | } 61 | return entries; 62 | } 63 | } 64 | 65 | PerDocumentCache[DocumentUri] caches; 66 | 67 | void update(DocumentUri uri) 68 | { 69 | import std.file : FileException; 70 | 71 | try 72 | { 73 | auto profileGC = documents.getOrFromFilesystem(uri); 74 | trace("Processing profilegc.log ", uri); 75 | auto entries = caches.require(uri).process(uri.uriDirName, profileGC.rawText); 76 | // trace("Processed: ", entries); 77 | } 78 | catch (FileException e) 79 | { 80 | trace("File Exception processing profilegc: ", e.msg); 81 | caches.remove(uri); 82 | } 83 | catch (Exception e) 84 | { 85 | trace("Exception processing profilegc: ", e); 86 | caches.remove(uri); 87 | } 88 | } 89 | 90 | void clear(DocumentUri uri) 91 | { 92 | trace("Clearing profilegc.log cache from ", uri); 93 | caches.remove(uri); 94 | } 95 | } 96 | 97 | package __gshared ProfileGCCache profileGCCache; 98 | 99 | @protocolMethod("textDocument/codeLens") 100 | CodeLens[] provideProfileGCCodeLens(CodeLensParams params) 101 | { 102 | if (!config(params.textDocument.uri).d.enableGCProfilerDecorations) 103 | return null; 104 | 105 | auto lenses = appender!(CodeLens[]); 106 | foreach (url, cache; profileGCCache.caches) 107 | { 108 | foreach (entry; cache.entries) 109 | { 110 | if (entry.uri == params.textDocument.uri) 111 | { 112 | lenses ~= CodeLens( 113 | TextRange(entry.line - 1, 0, entry.line - 1, 1), 114 | Command(format!"%s bytes allocated / %s allocations"(entry.bytesAllocated, entry.allocationCount)).opt 115 | ); 116 | } 117 | } 118 | } 119 | return lenses.data; 120 | } 121 | 122 | @protocolMethod("served/getProfileGCEntries") 123 | ProfileGCEntry[] getProfileGCEntries() 124 | { 125 | auto lenses = appender!(ProfileGCEntry[]); 126 | foreach (url, cache; profileGCCache.caches) 127 | lenses ~= cache.entries; 128 | return lenses.data; 129 | } 130 | 131 | @onRegisteredComponents 132 | void setupProfileGCWatchers() 133 | { 134 | if (capabilities 135 | .workspace.orDefault 136 | .didChangeWatchedFiles.orDefault 137 | .dynamicRegistration.orDefault) 138 | { 139 | if (!rpc.registerCapabilitySync( 140 | "profilegc.watchfiles", 141 | "workspace/didChangeWatchedFiles", 142 | DidChangeWatchedFilesRegistrationOptions( 143 | [ 144 | FileSystemWatcher("**/profilegc.log") 145 | ] 146 | ) 147 | )) 148 | error("Failed to dynamically register GC watchers"); 149 | } 150 | } 151 | 152 | @onProjectAvailable 153 | void onProfileGCProjectAvailable(WorkspaceD.Instance instance, string dir, string uri) 154 | { 155 | profileGCCache.update(uri.chomp("/") ~ "/profilegc.log"); 156 | } 157 | 158 | @protocolNotification("workspace/didChangeWatchedFiles") 159 | void onChangeProfileGC(DidChangeWatchedFilesParams params) 160 | { 161 | foreach (change; params.changes) 162 | { 163 | if (!change.uri.endsWith("profilegc.log")) 164 | continue; 165 | 166 | if (change.type == FileChangeType.created 167 | || change.type == FileChangeType.changed) 168 | profileGCCache.update(change.uri); 169 | else if (change.type == FileChangeType.deleted) 170 | profileGCCache.clear(change.uri); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /sponsors/weka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pure-D/serve-d/bd968dc2ab7bc6591b885c0ef5a6892a46ff1a91/sponsors/weka.png -------------------------------------------------------------------------------- /test.bat: -------------------------------------------------------------------------------- 1 | dub test :http && dub test :lsp && dub test :serverbase && dub test -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euo pipefail 3 | # hehe, this is the best kind of windows emulation 4 | # test.bat contains just a list of dub commands, so we don't have to maintain this file twice. 5 | . ./test.bat 6 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | */test-* 2 | testout.txt 3 | *.zip 4 | *.tar.gz 5 | dcd-client* 6 | dcd-server* 7 | dub.selections.json 8 | -------------------------------------------------------------------------------- /test/data/dcd/download_dcd.d: -------------------------------------------------------------------------------- 1 | import std.algorithm; 2 | import std.conv; 3 | import std.file; 4 | import std.format; 5 | import std.process; 6 | import std.stdio; 7 | import std.string; 8 | 9 | void downloadFile(string path, string url) 10 | { 11 | import std.net.curl : download; 12 | 13 | if (exists(path)) 14 | return; 15 | 16 | stderr.writeln("Downloading ", url, " to ", path); 17 | 18 | download(url, path); 19 | } 20 | 21 | void extractZip(string path) 22 | { 23 | import std.zip : ZipArchive; 24 | 25 | auto zip = new ZipArchive(read(path)); 26 | foreach (name, am; zip.directory) 27 | { 28 | stderr.writeln("Unpacking ", name); 29 | zip.expand(am); 30 | 31 | if (exists(name)) 32 | remove(name); 33 | 34 | std.file.write(name, am.expandedData); 35 | } 36 | } 37 | 38 | static immutable latestKnownVersion = (){ 39 | import workspaced.com.dcd_version : latestKnownDCDVersion; 40 | 41 | return latestKnownDCDVersion; 42 | }(); 43 | 44 | void main() 45 | { 46 | string ver = format!"%(%s.%)"(latestKnownVersion); 47 | string dcdClient = "dcd-client"; 48 | string dcdServer = "dcd-server"; 49 | version (Windows) 50 | { 51 | dcdClient ~= ".exe"; 52 | dcdServer ~= ".exe"; 53 | string zip = "dcd-" ~ ver ~ ".zip"; 54 | string url = format!"https://github.com/dlang-community/DCD/releases/download/v%s/dcd-v%s-windows-x86_64.zip"(ver, ver); 55 | void extract() 56 | { 57 | extractZip(zip); 58 | } 59 | } 60 | else version (linux) 61 | { 62 | string zip = "dcd-v" ~ ver ~ "-linux-x86_64.tar.gz"; 63 | string url = format!"https://github.com/dlang-community/DCD/releases/download/v%s/dcd-v%s-linux-x86_64.tar.gz"(ver, ver); 64 | void extract() 65 | { 66 | spawnShell("tar -xzvf " ~ zip ~ " > /dev/null").wait; 67 | } 68 | } 69 | else version (OSX) 70 | { 71 | version (AArch64) 72 | { 73 | string zip = "dcd-v" ~ ver ~ "-osx-arm64.tar.gz"; 74 | string url = format!"https://github.com/dlang-community/DCD/releases/download/v%s/dcd-v%s-osx-arm64.tar.gz"(ver, ver); 75 | } 76 | else 77 | { 78 | string zip = "dcd-v" ~ ver ~ "-osx-x86_64.tar.gz"; 79 | string url = format!"https://github.com/dlang-community/DCD/releases/download/v%s/dcd-v%s-osx-x86_64.tar.gz"(ver, ver); 80 | } 81 | void extract() 82 | { 83 | spawnShell("tar -xzvf " ~ zip ~ " > /dev/null").wait; 84 | } 85 | } 86 | 87 | if (!exists(zip)) 88 | { 89 | writeln("Downloading DCD ", ver); 90 | downloadFile(zip, url); 91 | } 92 | 93 | try { remove(dcdClient); } catch (FileException) { /* ignore */ } 94 | try { remove(dcdServer); } catch (FileException) { /* ignore */ } 95 | 96 | extract(); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /test/data/list_definition/_basic.d: -------------------------------------------------------------------------------- 1 | module foo.bar; 2 | 3 | version = Foo; 4 | debug = Bar; 5 | 6 | void hello() { 7 | int x = 1; 8 | } 9 | 10 | int y = 2; 11 | 12 | int 13 | bar() 14 | { 15 | } 16 | 17 | unittest 18 | { 19 | } 20 | 21 | @( "named" ) 22 | unittest 23 | { 24 | } 25 | 26 | private class X 27 | { 28 | this(int x) {} 29 | this(this) {} 30 | ~this() {} 31 | 32 | unittest 33 | { 34 | } 35 | } 36 | 37 | shared static this() 38 | { 39 | } 40 | 41 | __EOF__ 42 | hello 6 f {"signature": "()", "access": "public", "return": "void"} 46 74 43 | y 10 v {"access": "public", "detail": "int"} 76 86 44 | bar 13 f {"signature": "()", "access": "public", "return": "int"} 88 101 45 | X 26 c {"access": "private", "detail": "class"} 152 223 46 | this 28 f {"signature": "(int x)", "access": "public", "class": "X"} 163 177 47 | ~this 30 f {"access": "public", "class": "X"} 194 204 48 | -------------------------------------------------------------------------------- /test/data/list_definition/_verbose.d: -------------------------------------------------------------------------------- 1 | module foo.bar; 2 | 3 | version = Foo; 4 | debug = Bar; 5 | 6 | void hello() { 7 | int x = 1; 8 | } 9 | 10 | int y = 2; 11 | 12 | int 13 | bar() 14 | { 15 | } 16 | 17 | deprecated unittest 18 | { 19 | } 20 | 21 | @( "named" ) 22 | unittest 23 | { 24 | } 25 | 26 | class X 27 | { 28 | this(int x) {} 29 | this(this) {} 30 | ~this() {} 31 | 32 | unittest 33 | { 34 | } 35 | } 36 | 37 | shared static this() 38 | { 39 | } 40 | 41 | __EOF__ 42 | :verbose=true 43 | Foo 3 V {"access": "public"} 17 31 44 | Bar 4 D {"access": "public"} 32 44 45 | hello 6 f {"signature": "()", "access": "public", "return": "void"} 46 74 46 | y 10 v {"access": "public", "detail": "int"} 76 86 47 | bar 13 f {"signature": "()", "access": "public", "return": "int"} 88 101 48 | __unittest_L17_C12 17 U {"deprecation":"", "access": "public"} 114 126 49 | __unittest_L22_C1 22 U {"access": "public", "detail": "named"} 141 153 50 | X 26 c {"access": "public", "detail": "class"} 155 226 51 | this 28 f {"signature": "(int x)", "access": "public", "class": "X"} 166 180 52 | this(this) 29 f {"access": "public", "class": "X"} 182 195 53 | ~this 30 f {"access": "public", "class": "X"} 197 207 54 | __unittest_L32_C2 32 U {"access": "public", "class": "X"} 210 224 55 | shared static this() 37 S {"access": "public"} 228 252 56 | -------------------------------------------------------------------------------- /test/data/list_definition/static_ctors.d: -------------------------------------------------------------------------------- 1 | public class Foo : Bar { 2 | static this() { 3 | for (int i; i < 256; i++) { 4 | arr[i] = i; 5 | } 6 | } 7 | 8 | int[256] arr; 9 | } 10 | __EOF__ 11 | :verbose=true 12 | Foo 1 c {"access":"public", "detail":"class"} 7 111 13 | static this() 2 C {"access": "public", "class": "Foo"} 26 93 14 | arr 8 v {"access":"public", "class":"Foo", "detail":"int[256]"} 96 109 15 | -------------------------------------------------------------------------------- /test/data/snippet_info/_basic.d: -------------------------------------------------------------------------------- 1 | /// this is a cool module 2 | module something; 3 | 4 | // todo stuff 5 | 6 | /// a lot of functionality 7 | void foo() 8 | { 9 | writeln("hello world"); 10 | } 11 | 12 | int main(string[] args) 13 | { 14 | foo(); 15 | } 16 | 17 | // TODO: comment snippets not yet implemented 18 | 19 | __EOF__ 20 | 0 global 21 | 1 global 22 | // 2 comment 23 | // 3 docComment 24 | // 10 docComment 25 | // 25 docComment 26 | 26 global 27 | // 47 comment 28 | // 50 comment 29 | // 58 comment 30 | 59 global 31 | 60 global 32 | // 63 docComment 33 | 109 value 34 | 110 strings 35 | 111 strings 36 | 121 strings 37 | 122 other 38 | -------------------------------------------------------------------------------- /test/data/snippet_info/dot_exception.d: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | foo. 4 | 5 | bar(); 6 | } 7 | 8 | void main() 9 | { 10 | foo.bar 11 | 12 | bar(); 13 | } 14 | 15 | __EOF__ 16 | 19 other 17 | 51 other 18 | 52 other 19 | 54 other 20 | -------------------------------------------------------------------------------- /test/data/snippet_info/identifiers.d: -------------------------------------------------------------------------------- 1 | // ugly indents in this file to make sure there is whitespace at these locations 2 | 3 | void main() 4 | { 5 | foo(); 6 | 7 | bar(); 8 | } 9 | 10 | void foo() 11 | { 12 | bar(); 13 | 14 | } 15 | 16 | void bar() 17 | { 18 | } 19 | 20 | void main() 21 | { 22 | foo(); 23 | 24 | half 25 | 26 | bar(); 27 | } 28 | 29 | void main() 30 | { 31 | foo(); 32 | 33 | while 34 | 35 | bar(); 36 | } 37 | 38 | __EOF__ 39 | 106 method 40 | 140 method 41 | 158 method 42 | 186 method 43 | 188 method 44 | 190 method 45 | 191 other 46 | 227 method 47 | 230 method 48 | 232 method 49 | 233 other 50 | 51 | 101 value 52 | 102 other 53 | -------------------------------------------------------------------------------- /test/data/snippet_info/traits.d: -------------------------------------------------------------------------------- 1 | void foo() 2 | { 3 | static if (__traits(foo)) 4 | { 5 | } 6 | } 7 | 8 | void bar() 9 | { 10 | static if (__traits()) 11 | { 12 | } 13 | } 14 | 15 | __EOF__ 16 | 34 other 17 | 36 other 18 | 37 other 19 | 83 other 20 | -------------------------------------------------------------------------------- /test/runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | RED="\033[31m" 5 | GREEN="\033[32m" 6 | YELLOW="\033[33m" 7 | NORMAL="\033[0m" 8 | 9 | if [ -z "${DC-}" ]; then 10 | DC="dmd" 11 | fi 12 | 13 | fail_count=0 14 | pass_count=0 15 | 16 | inCIEnvironment () [[ ! -z "${CI:-}" ]] 17 | 18 | echo "Compiling serve-d in release mode with ${DC}..." 19 | 20 | inCIEnvironment && echo "::group::Building serve-d" 21 | pushd .. 22 | if [ "$DC" = "dmd" ]; then 23 | echo "(Debug build because using DMD)" 24 | dub build --compiler="${DC}" 25 | else 26 | dub build --build=release --compiler="${DC}" 27 | fi 28 | popd 29 | inCIEnvironment && echo "::endgroup::" 30 | 31 | tests="${@:1}" 32 | if [ -z "$tests" ]; then 33 | tests=tc* 34 | fi 35 | 36 | echo "Running tests with ${DC}..." 37 | 38 | for testCase in $tests; do 39 | # GitHub Actions grouping 40 | echo -e "${YELLOW}$testCase${NORMAL}" 41 | 42 | inCIEnvironment && echo "::group::$testCase" 43 | pushd $testCase 44 | 45 | if [ -f .needs_dcd ]; then 46 | pushd ../data/dcd 47 | $DC -I../../../workspace-d/source -run download_dcd.d 48 | popd 49 | cp ../data/dcd/dcd-server* . 50 | cp ../data/dcd/dcd-client* . 51 | fi 52 | 53 | dub upgrade 2>&1 54 | 55 | dub --compiler="${DC}" 2>&1 56 | if [[ $? -eq 0 ]]; then 57 | inCIEnvironment && echo "::endgroup::" 58 | echo -e "${YELLOW}$testCase:${NORMAL} ... ${GREEN}Test Pass${NORMAL}"; 59 | let pass_count=pass_count+1 60 | else 61 | inCIEnvironment && echo "::endgroup::" 62 | echo -e "${YELLOW}$testCase:${NORMAL} ... ${RED}Test Fail${NORMAL}"; 63 | let fail_count=fail_count+1 64 | fi 65 | 66 | popd 67 | done 68 | 69 | if [[ $fail_count -eq 0 ]]; then 70 | echo -e "${GREEN}${pass_count} tests passed and ${fail_count} failed.${NORMAL}" 71 | else 72 | echo -e "${RED}${pass_count} tests passed and ${fail_count} failed.${NORMAL}" 73 | exit 1 74 | fi 75 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-as-a-exe" 2 | 3 | dependency "serve-d:lsp" path="../.." 4 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/source/app.d: -------------------------------------------------------------------------------- 1 | import std.bitmanip; 2 | import std.conv; 3 | import std.experimental.logger; 4 | import std.file; 5 | import std.functional; 6 | import std.json; 7 | import std.path; 8 | import std.process; 9 | import std.stdio; 10 | import std.string; 11 | import std.uuid; 12 | 13 | import core.thread; 14 | import core.thread.fiber; 15 | 16 | import served.lsp.filereader; 17 | import served.lsp.jsonops; 18 | import served.lsp.jsonrpc; 19 | import served.lsp.protocol; 20 | import served.lsp.uri; 21 | 22 | import tests._basic; 23 | 24 | version (assert) 25 | { 26 | } 27 | else 28 | static assert(false, "Compile with asserts."); 29 | 30 | void main() 31 | { 32 | version (Windows) 33 | string exe = `..\..\serve-d.exe`; 34 | else 35 | string exe = `../../serve-d`; 36 | 37 | globalLogLevel = LogLevel.all; 38 | 39 | foreach (test; [ 40 | new BasicTests(exe) 41 | ]) 42 | test.run(); 43 | } 44 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/source/tests/_basic.d: -------------------------------------------------------------------------------- 1 | module tests._basic; 2 | 3 | import tests; 4 | 5 | class BasicTests : ServedInstancedTest 6 | { 7 | this(string servedExe) 8 | { 9 | super(servedExe, buildPath(__FILE_FULL_PATH__, "../..")); 10 | } 11 | 12 | override void runImpl() 13 | { 14 | // dfmt off 15 | WorkspaceClientCapabilities workspace = { 16 | configuration: opt(true) 17 | }; 18 | InitializeParams init = InitializeParams( 19 | processId: typeof(InitializeParams.processId)(thisProcessID), 20 | rootUri: uriFromFile(cwd), 21 | capabilities: ClientCapabilities( 22 | workspace: opt(workspace) 23 | ) 24 | ); 25 | // dfmt on 26 | auto msg = rpc.sendRequest("initialize", init, 10.seconds); 27 | info("Response: ", msg.resultJson); 28 | 29 | info("Shutting down..."); 30 | rpc.sendRequest("shutdown", init); 31 | pumpEvents(); 32 | Fiber.yield(); 33 | rpc.notifyMethod("exit"); 34 | pumpEvents(); 35 | Thread.sleep(2.seconds); // give serve-d a chance to clean up 36 | Fiber.yield(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/source/tests/package.d: -------------------------------------------------------------------------------- 1 | module tests; 2 | 3 | public import core.thread : Fiber, Thread; 4 | public import core.time; 5 | public import served.lsp.jsonrpc; 6 | public import served.lsp.protocol; 7 | public import served.lsp.uri; 8 | public import std.conv; 9 | public import std.experimental.logger; 10 | public import std.path; 11 | public import std.process : thisProcessID; 12 | import std.functional : toDelegate; 13 | 14 | abstract class ServedTest 15 | { 16 | this(string servedExe) 17 | { 18 | gotRequest = toDelegate(&defaultRequestHandler); 19 | gotNotify = toDelegate(&defaultNotifyHandler); 20 | this.servedExe = servedExe; 21 | } 22 | 23 | abstract void run(); 24 | 25 | void tick() 26 | { 27 | pumpEvents(); 28 | Fiber.yield(); 29 | } 30 | 31 | void processResponse(void delegate(RequestMessageRaw msg) cb) 32 | { 33 | bool called; 34 | gotRequest = (msg) { called = true; cb(msg); }; 35 | tick(); 36 | assert(called, "no response received!"); 37 | gotRequest = null; 38 | } 39 | 40 | void pumpEvents() 41 | { 42 | while (rpc.hasData) 43 | { 44 | auto msg = rpc.poll; 45 | if (!msg.id.isNone) 46 | gotRequest(msg); 47 | else 48 | gotNotify(msg); 49 | } 50 | } 51 | 52 | protected string servedExe; 53 | 54 | RPCProcessor rpc; 55 | 56 | void delegate(RequestMessageRaw msg) gotRequest; 57 | void delegate(RequestMessageRaw msg) gotNotify; 58 | } 59 | 60 | void defaultRequestHandler(RequestMessageRaw msg) 61 | { 62 | assert(false, "Unexpected request " ~ msg.toString); 63 | } 64 | 65 | void defaultNotifyHandler(RequestMessageRaw msg) 66 | { 67 | info("Ignoring notification " ~ msg.toString); 68 | } 69 | 70 | abstract class ServedInstancedTest : ServedTest 71 | { 72 | import std.file; 73 | import std.process; 74 | 75 | string templateDir; 76 | string cwd; 77 | 78 | this(string servedExe, string templateDir) 79 | { 80 | super(servedExe); 81 | this.templateDir = templateDir; 82 | } 83 | 84 | override void run() 85 | { 86 | import served.lsp.filereader; 87 | import std.uuid; 88 | 89 | cwd = buildNormalizedPath(tempDir, randomUUID.toString); 90 | copyDir("template", cwd); 91 | info("temporary CWD: ", cwd); 92 | scope (failure) 93 | info("Keeping temporary directory for debugging purposes"); 94 | scope (success) 95 | rmdirRecurse(cwd); 96 | 97 | auto proc = pipeProcess([servedExe, "--loglevel=all"], Redirect.stdin | 98 | Redirect.stdout); 99 | 100 | auto reader = newFileReader(proc.stdout); 101 | reader.start(); 102 | scope (exit) 103 | reader.stop(); 104 | rpc = new RPCProcessor(reader, proc.stdin); 105 | auto testMethod = new Fiber(&runImpl); 106 | do 107 | { 108 | rpc.call(); 109 | if (testMethod.state == Fiber.State.TERM) 110 | { 111 | if (rpc.state == Fiber.State.TERM) 112 | break; 113 | assert(false, "doTests exitted too early "); 114 | } 115 | testMethod.call(); 116 | Thread.sleep(1.msecs); 117 | } 118 | while (rpc.state != Fiber.State.TERM); 119 | assert(testMethod.state == Fiber.State.TERM); 120 | auto exitCode = proc.pid.wait; 121 | assert(exitCode == 0, "serve-d failed with exit code " ~ exitCode.to!string); 122 | } 123 | 124 | abstract void runImpl(); 125 | } 126 | 127 | // https://forum.dlang.org/post/akucvkduasjlwgykkrzs@forum.dlang.org 128 | void copyDir(string inDir, string outDir) 129 | { 130 | import std.file; 131 | import std.format; 132 | 133 | if (!exists(outDir)) 134 | mkdir(outDir); 135 | else if (!isDir(outDir)) 136 | throw new FileException(format("Destination path %s is not a folder.", outDir)); 137 | 138 | foreach (entry; dirEntries(inDir.idup, SpanMode.shallow)) 139 | { 140 | auto fileName = baseName(entry.name); 141 | auto destName = buildPath(outDir, fileName); 142 | if (entry.isDir()) 143 | copyDir(entry.name, destName); 144 | else 145 | copy(entry.name, destName); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/template/README: -------------------------------------------------------------------------------- 1 | this folder is used in the test as starting off point 2 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/template/dub.sdl: -------------------------------------------------------------------------------- 1 | name "template" 2 | -------------------------------------------------------------------------------- /test/tc_as_a_exe/template/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main(string[] args) 4 | { 5 | writeln("hello world"); 6 | } -------------------------------------------------------------------------------- /test/tc_dub/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dub" 2 | 3 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_dub/source/app.d: -------------------------------------------------------------------------------- 1 | import std.conv : to; 2 | import std.file; 3 | import std.path; 4 | import std.stdio; 5 | import std.string; 6 | 7 | import workspaced.api; 8 | import workspaced.com.dub; 9 | 10 | void main() 11 | { 12 | string dir = buildNormalizedPath(getcwd, "..", "tc_fsworkspace"); 13 | scope backend = new WorkspaceD(); 14 | auto instance = backend.addInstance(dir); 15 | backend.register!DubComponent; 16 | 17 | auto dub = backend.get!DubComponent(dir); 18 | 19 | dub.upgradeAndSelectAll(); 20 | assert(dub.dependencies.length > 2); 21 | assert(dub.rootDependencies == ["serve-d:workspace-d"]); 22 | // this can be 23 | // tc_fsworkspace/source, workspace-d/source 24 | // if no dependencies are fetched 25 | // or with all dependencies there a lot more 26 | assert(dub.imports.length >= 2, dub.imports.to!string); 27 | assert(dub.stringImports[$ - 1].endsWith("views") 28 | || dub.stringImports[$ - 1].endsWith("views/") 29 | || dub.stringImports[$ - 1].endsWith("views\\"), 30 | dub.stringImports.to!string ~ " doesn't end with `views`!"); 31 | assert(dub.fileImports.length > 10); 32 | assert(dub.configurations.length == 2); 33 | assert(dub.buildTypes.length); 34 | assert(dub.configuration == "application"); 35 | assert(dub.archTypes.length); 36 | assert(!dub.archType.length); // compiler default (null) has been default since serve-d 0.8.0 37 | assert(dub.buildType == "debug"); 38 | assert(dub.compiler.length); 39 | assert(dub.name == "test-fsworkspace"); 40 | assert(dub.path.toString.endsWith("tc_fsworkspace") 41 | || dub.path.toString.endsWith("tc_fsworkspace/") 42 | || dub.path.toString.endsWith("tc_fsworkspace\\")); 43 | try 44 | { 45 | auto result = dub.build.getBlocking; 46 | assert(result.count!(a => a.type == ErrorType.Warning || a.type == ErrorType.Error) == 0, 47 | "got unexpected warnings/errors: " ~ result.to!string); 48 | } 49 | catch (Exception e) 50 | { 51 | stderr.writeln("Not testing build because it throws: ", e); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/tc_dub_dependencies/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dub-dependencies" 2 | dependency "serve-d:workspace-d" path="../.." 3 | -------------------------------------------------------------------------------- /test/tc_dub_dependencies/project/dub.sdl: -------------------------------------------------------------------------------- 1 | name "project" 2 | -------------------------------------------------------------------------------- /test/tc_dub_dependencies/project/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | void main(string[] args) 4 | { 5 | writeln("Hello world"); 6 | } 7 | -------------------------------------------------------------------------------- /test/tc_dub_dependencies/source/app.d: -------------------------------------------------------------------------------- 1 | module app; 2 | 3 | import std.conv : to; 4 | import fs = std.file; 5 | import std.path; 6 | import std.process; 7 | import std.stdio; 8 | import std.string; 9 | 10 | import workspaced.api; 11 | import workspaced.com.dub; 12 | 13 | void main() 14 | { 15 | { 16 | import std.logger; 17 | globalLogLevel = LogLevel.trace; 18 | static if (__VERSION__ < 2101) 19 | sharedLog = new FileLogger(stderr); 20 | else 21 | sharedLog = (() @trusted => cast(shared) new FileLogger(stderr))(); 22 | } 23 | 24 | string dir = buildNormalizedPath(fs.getcwd, "project"); 25 | 26 | fs.write(buildPath(dir, "dub.sdl"), "name \"project\"\n"); 27 | 28 | scope backend = new WorkspaceD(); 29 | backend.addInstance(dir); 30 | backend.register!DubComponent; 31 | 32 | import dub.internal.logging : LogLevel, setLogLevel; 33 | setLogLevel(LogLevel.debug_); 34 | 35 | version (Posix) 36 | { 37 | if (fs.exists(expandTilde(`~/.dub/packages/gitcompatibledubpackage-1.0.4/`))) 38 | fs.rmdirRecurse(expandTilde(`~/.dub/packages/gitcompatibledubpackage-1.0.4/`)); 39 | if (fs.exists(expandTilde(`~/.dub/packages/gitcompatibledubpackage/1.0.4/`))) 40 | fs.rmdirRecurse(expandTilde(`~/.dub/packages/gitcompatibledubpackage/1.0.4/`)); 41 | } 42 | 43 | auto dub = backend.get!DubComponent(dir); 44 | 45 | assert(dub.rootDependencies.length == 0); 46 | dub.selectAndDownloadMissing(); 47 | assert(dub.rootDependencies.length == 0); 48 | 49 | fs.write(buildPath(dir, "dub.sdl"), "name \"project\"\ndependency \"gitcompatibledubpackage\" version=\"1.0.4\"\n"); 50 | 51 | dub.updateImportPaths(); 52 | assert(dub.missingDependencies.length == 1); 53 | assert(dub.rootDependencies.length == 1); 54 | dub.selectAndDownloadMissing(); 55 | assert(dub.missingDependencies.length == 0); 56 | assert(dub.rootDependencies.length == 1); 57 | assert(dub.rootDependencies[0] == "gitcompatibledubpackage"); 58 | 59 | fs.write(buildPath(dir, "dub.sdl"), "name \"project\"\n"); 60 | 61 | assert(dub.rootDependencies.length == 1); 62 | dub.updateImportPaths(); 63 | assert(dub.rootDependencies.length == 0); 64 | } 65 | -------------------------------------------------------------------------------- /test/tc_dub_empty/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dub-empty" 2 | 3 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_dub_empty/empty1/dub.sdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pure-D/serve-d/bd968dc2ab7bc6591b885c0ef5a6892a46ff1a91/test/tc_dub_empty/empty1/dub.sdl -------------------------------------------------------------------------------- /test/tc_dub_empty/empty2/dub.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/tc_dub_empty/empty3/dub.sdl: -------------------------------------------------------------------------------- 1 | name "empty3" -------------------------------------------------------------------------------- /test/tc_dub_empty/empty_windows/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty-windows", 3 | "configurations": [ 4 | { 5 | "name": "empty-windows", 6 | "platforms": [ 7 | "posix" 8 | ] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /test/tc_dub_empty/invalid/dub.json: -------------------------------------------------------------------------------- 1 | {"a":/ -------------------------------------------------------------------------------- /test/tc_dub_empty/missing_dep/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dub-missing-dep" 2 | 3 | dependency "workspace-d:nonexistant" version="*" 4 | -------------------------------------------------------------------------------- /test/tc_dub_empty/missing_dep/source/app.d: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | } -------------------------------------------------------------------------------- /test/tc_dub_empty/source/app.d: -------------------------------------------------------------------------------- 1 | import Compiler = std.compiler; 2 | import std.file; 3 | import std.json; 4 | import std.path; 5 | import std.stdio; 6 | import std.string; 7 | import std.traits; 8 | 9 | import workspaced.api; 10 | import workspaced.coms; 11 | 12 | WorkspaceD backend; 13 | 14 | void main(string[] args) 15 | { 16 | import std.experimental.logger; 17 | 18 | globalLogLevel = LogLevel.trace; 19 | 20 | static if (__VERSION__ < 2101) 21 | sharedLog = new FileLogger(stderr); 22 | else 23 | sharedLog = (() @trusted => cast(shared) new FileLogger(stderr))(); 24 | 25 | string dir = buildNormalizedPath(getcwd, "..", "tc_fsworkspace"); 26 | backend = new WorkspaceD(); 27 | backend.register!DubComponent; 28 | scope (exit) 29 | backend.shutdown(); 30 | 31 | if (args.length > 1) 32 | { 33 | foreach (folder; args[1 .. $]) 34 | if (!tryDub(folder, false)) 35 | throw new Exception("Failed " ~ folder); 36 | 37 | stderr.writeln("All inputs passed!"); 38 | } 39 | else 40 | { 41 | assert(tryDub("valid")); 42 | assert(!tryDub("empty1")); 43 | assert(!tryDub("empty2")); 44 | assert(tryDub("empty3")); 45 | assert(!tryDub("invalid")); 46 | assert(tryDub("sourcelib")); 47 | version (Windows) 48 | assert(tryDub("empty_windows")); 49 | assert(tryDub("missing_dep")); 50 | stderr.writeln("Success!"); 51 | } 52 | } 53 | 54 | bool tryDub(string path, bool clean = true) 55 | { 56 | DubComponent dub; 57 | try 58 | { 59 | if (clean) 60 | { 61 | if (exists(buildNormalizedPath(getcwd, path, ".dub"))) 62 | rmdirRecurse(buildNormalizedPath(getcwd, path, ".dub")); 63 | if (exists(buildNormalizedPath(getcwd, path, "dub.selections.json"))) 64 | remove(buildNormalizedPath(getcwd, path, "dub.selections.json")); 65 | } 66 | 67 | auto dir = buildNormalizedPath(getcwd, path); 68 | backend.addInstance(dir); 69 | dub = backend.get!DubComponent(dir); 70 | } 71 | catch (Exception e) 72 | { 73 | stderr.writeln(path, ": ", e.msg); 74 | return false; 75 | } 76 | 77 | auto tryRun(string fn, Args...)(string trace, Args args) 78 | { 79 | try 80 | { 81 | static if (is(typeof(mixin("dub." ~ fn ~ "(args)")) == void)) 82 | { 83 | mixin("dub." ~ fn ~ "(args);"); 84 | stderr.writeln(trace, ": pass ", fn); 85 | return; 86 | } 87 | else 88 | { 89 | auto ret = mixin("dub." ~ fn ~ "(args)"); 90 | stderr.writeln(trace, ": pass ", fn, " = ", ret); 91 | return ret; 92 | } 93 | } 94 | catch (Exception e) 95 | { 96 | stderr.writeln(trace, ": failed to run ", fn, ": ", e.msg); 97 | static if (!is(typeof(return) == void)) 98 | return typeof(return).init; 99 | } 100 | catch (Error e) 101 | { 102 | stderr.writeln(trace, ": assert error in ", fn, ": ", e.msg); 103 | throw e; 104 | } 105 | } 106 | 107 | foreach (step; 0 .. clean ? 2 : 1) 108 | { 109 | stderr.writeln("step #", step + 1, ": ", path); 110 | tryRun!"isValidConfiguration"(path); 111 | if (clean) 112 | tryRun!"upgradeAndSelectAll"(path); 113 | else 114 | tryRun!"selectAndDownloadMissing"(path); 115 | tryRun!"isValidConfiguration"(path); 116 | tryRun!"dependencies"(path); 117 | tryRun!"rootDependencies"(path); 118 | tryRun!"imports"(path); 119 | tryRun!"stringImports"(path); 120 | tryRun!"fileImports"(path); 121 | tryRun!"configurations"(path); 122 | tryRun!"buildTypes"(path); 123 | tryRun!"configuration"(path); 124 | tryRun!"setConfiguration"(path, dub.configuration); 125 | tryRun!"archTypes"(path); 126 | tryRun!"archType"(path); 127 | tryRun!"setArchType"(path, "x86"); 128 | tryRun!"buildType"(path); 129 | tryRun!"setBuildType"(path, "debug"); 130 | tryRun!"compiler"(path); 131 | static if (Compiler.vendor == Compiler.Vendor.gnu) 132 | tryRun!"setCompiler"(path, "gdc"); 133 | else static if (Compiler.vendor == Compiler.Vendor.llvm) 134 | tryRun!"setCompiler"(path, "ldc2"); 135 | else 136 | tryRun!"setCompiler"(path, "dmd"); 137 | tryRun!"name"(path); 138 | tryRun!"path"(path); 139 | tryRun!"build.getBlocking"(path); 140 | // restart 141 | tryRun!"update.getBlocking"(path); 142 | } 143 | 144 | return true; 145 | } 146 | -------------------------------------------------------------------------------- /test/tc_dub_empty/sourcelib/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sourcelib", 3 | "targetType": "sourceLibrary" 4 | } -------------------------------------------------------------------------------- /test/tc_dub_empty/sourcelib/source/lib.d: -------------------------------------------------------------------------------- 1 | module lib; 2 | 3 | void foo() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /test/tc_dub_empty/valid/dub.sdl: -------------------------------------------------------------------------------- 1 | name "valid" -------------------------------------------------------------------------------- /test/tc_dub_empty/valid/source/app.d: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | } 4 | -------------------------------------------------------------------------------- /test/tc_fsworkspace/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-fsworkspace" 2 | 3 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_fsworkspace/source/app.d: -------------------------------------------------------------------------------- 1 | import std.file; 2 | import std.string; 3 | 4 | import workspaced.api; 5 | import workspaced.coms; 6 | 7 | void main() 8 | { 9 | string dir = getcwd; 10 | scope backend = new WorkspaceD(); 11 | auto instance = backend.addInstance(dir); 12 | backend.register!FSWorkspaceComponent; 13 | 14 | auto fsworkspace = backend.get!FSWorkspaceComponent(dir); 15 | 16 | assert(instance.importPaths == [getcwd]); 17 | fsworkspace.addImports(["source"]); 18 | assert(instance.importPaths == [getcwd, "source"]); 19 | } 20 | -------------------------------------------------------------------------------- /test/tc_implement_interface/.needs_dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pure-D/serve-d/bd968dc2ab7bc6591b885c0ef5a6892a46ff1a91/test/tc_implement_interface/.needs_dcd -------------------------------------------------------------------------------- /test/tc_implement_interface/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-implement-interface" 2 | 3 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_implement_interface/source/app.d: -------------------------------------------------------------------------------- 1 | import std.algorithm; 2 | import std.conv; 3 | import std.file; 4 | import std.stdio; 5 | import std.string; 6 | import std.process; 7 | 8 | import workspaced.api; 9 | import workspaced.coms; 10 | 11 | int main(string[] args) 12 | { 13 | string dir = getcwd; 14 | scope backend = new WorkspaceD(); 15 | auto instance = backend.addInstance(dir); 16 | backend.register!FSWorkspaceComponent; 17 | backend.register!DCDComponent(false); 18 | backend.register!DCDExtComponent; 19 | 20 | version (Windows) 21 | { 22 | if (exists("dcd-client.exe")) 23 | backend.globalConfiguration.set("dcd", "clientPath", "dcd-client.exe"); 24 | 25 | if (exists("dcd-server.exe")) 26 | backend.globalConfiguration.set("dcd", "serverPath", "dcd-server.exe"); 27 | } 28 | else 29 | { 30 | if (exists("dcd-client")) 31 | backend.globalConfiguration.set("dcd", "clientPath", "dcd-client"); 32 | 33 | if (exists("dcd-server")) 34 | backend.globalConfiguration.set("dcd", "serverPath", "dcd-server"); 35 | } 36 | 37 | bool verbose = args.length > 1 && (args[1] == "-v" || args[1] == "--v" || args[1] == "--verbose"); 38 | 39 | assert(backend.attachSilent(instance, "dcd"), "failed to attach DCD, is it not installed correctly?"); 40 | 41 | auto fsworkspace = backend.get!FSWorkspaceComponent(dir); 42 | auto dcd = backend.get!DCDComponent(dir); 43 | auto dcdext = backend.get!DCDExtComponent(dir); 44 | 45 | fsworkspace.addImports(["source"]); 46 | 47 | auto port = dcd.findAndSelectPort(cast(ushort) 9166).getBlocking; 48 | instance.config.set("dcd", "port", cast(int) port); 49 | 50 | dcd.setupServer([], true); 51 | scope (exit) 52 | { 53 | dcd.stopServerSync(); 54 | backend.shutdown(); 55 | } 56 | 57 | stderr.writeln("DCD client version: ", dcd.clientInstalledVersion); 58 | 59 | int status = 0; 60 | 61 | foreach (test; dirEntries("tests", SpanMode.shallow)) 62 | { 63 | if (!test.name.endsWith(".d")) 64 | continue; 65 | auto expect = test ~ ".expected"; 66 | auto actualFile = test ~ ".actual"; 67 | if (!expect.exists) 68 | { 69 | stderr.writeln("Warning: tests/", expect, " does not exist!"); 70 | continue; 71 | } 72 | auto source = test.readText; 73 | auto reader = File(expect).byLine; 74 | auto writer = File(actualFile, "w"); 75 | auto cmd = reader.front.splitter; 76 | string code, message; 77 | bool success; 78 | if (cmd.front == "implement") 79 | { 80 | writer.writeln("# ", reader.front); 81 | 82 | cmd.popFront; 83 | auto cmdLine = cmd.front; 84 | code = dcdext.implement(source, cmdLine.parse!uint, false).getBlocking; 85 | reader.popFront; 86 | 87 | writer.writeln(code); 88 | writer.writeln(); 89 | writer.writeln(); 90 | 91 | if (verbose) 92 | stderr.writeln(test, ": ", code); 93 | 94 | success = true; 95 | size_t index; 96 | foreach (line; reader) 97 | { 98 | if (line.startsWith("--- ") || !line.length) 99 | continue; 100 | 101 | if (line.startsWith("!")) 102 | { 103 | if (code.indexOf(line[1 .. $], index) != -1) 104 | { 105 | writer.writeln(line, " - FAIL"); 106 | success = false; 107 | message = "Did not expect to find line " ~ line[1 .. $].idup 108 | ~ " in (after " ~ index.to!string ~ " bytes) code " ~ code[index .. $]; 109 | } 110 | } 111 | else if (line.startsWith("#")) 112 | { 113 | // count occurences 114 | line = line[1 .. $]; 115 | char op = line[0]; 116 | if (!op.among!('<', '=', '>')) 117 | throw new Exception("Malformed count line: " ~ line.idup); 118 | line = line[1 .. $]; 119 | int expected = line.parse!uint; 120 | line = line[1 .. $]; 121 | int actual = countText(code[index .. $], line); 122 | bool match; 123 | if (op == '<') 124 | match = actual < expected; 125 | else if (op == '=') 126 | match = actual == expected; 127 | else if (op == '>') 128 | match = actual > expected; 129 | else 130 | assert(false); 131 | if (!match) 132 | { 133 | writer.writeln(line, " - FAIL"); 134 | success = false; 135 | message = "Expected to find the string '" ~ line.idup ~ "' " ~ op ~ " " ~ expected.to!string 136 | ~ " times but actually found it " ~ actual.to!string 137 | ~ " times (after " ~ index.to!string ~ " bytes) code " ~ code[index .. $]; 138 | } 139 | } 140 | else 141 | { 142 | bool freeze = false; 143 | if (line.startsWith(".")) 144 | { 145 | freeze = true; 146 | line = line[1 .. $]; 147 | } 148 | auto pos = code.indexOf(line, index); 149 | if (pos == -1) 150 | { 151 | writer.writeln(line, " - FAIL"); 152 | success = false; 153 | message = "Could not find " ~ line.idup ~ " in remaining (after " 154 | ~ index.to!string ~ " bytes) code " ~ code[index .. $]; 155 | } 156 | else if (!freeze) 157 | { 158 | index = pos + line.length; 159 | } 160 | } 161 | } 162 | } 163 | else if (cmd.front == "failimplement") 164 | { 165 | writer.writeln("# ", reader.front); 166 | 167 | cmd.popFront; 168 | auto cmdLine = cmd.front; 169 | code = dcdext.implement(source, cmdLine.parse!uint, false).getBlocking; 170 | if (code.length != 0) 171 | { 172 | writer.writeln("unexpected: ", code); 173 | writer.writeln(); 174 | message = "Code: " ~ code; 175 | success = false; 176 | } 177 | else 178 | { 179 | writer.write("ok\n\n"); 180 | success = true; 181 | } 182 | } 183 | else 184 | throw new Exception("Unknown command in " ~ expect ~ ": " ~ reader.front.idup); 185 | 186 | if (success) 187 | { 188 | writer.close(); 189 | std.file.remove(actualFile); 190 | writeln("Pass ", expect); 191 | } 192 | else 193 | { 194 | writer.writeln("-----------------\n\nTest failed\n\n-----------------\n\n"); 195 | writer.writeln(message); 196 | writeln("Fail ", expect, ": ", message); 197 | status = 1; 198 | } 199 | } 200 | 201 | return status; 202 | } 203 | 204 | int countText(in char[] text, in char[] search) 205 | { 206 | int num = 0; 207 | ptrdiff_t index = text.indexOf(search); 208 | while (index != -1) 209 | { 210 | num++; 211 | index = text.indexOf(search, index + search.length); 212 | } 213 | return num; 214 | } 215 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/basic.d: -------------------------------------------------------------------------------- 1 | module basic; 2 | 3 | class Bar : Foo 4 | { 5 | } 6 | 7 | class Foo : Foo0 8 | { 9 | void virtualMethod(); 10 | abstract int abstractMethod(string s) { return cast(int) s.length; } 11 | } 12 | 13 | import std.container.array; 14 | import std.typecons; 15 | package interface Foo0 : Foo1, Foo2 16 | { 17 | string stringMethod(); 18 | Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c); 19 | void normalMethod(); 20 | int attributeSuffixMethod() nothrow @property @nogc; 21 | private 22 | { 23 | void middleprivate1(); 24 | void middleprivate2(); 25 | } 26 | extern(C) @property @nogc ref immutable int attributePrefixMethod() const; 27 | final void alreadyImplementedMethod() {} 28 | deprecated("foo") void deprecatedMethod() {} 29 | static void staticMethod() {} 30 | protected void protectedMethod(); 31 | private: 32 | void barfoo(); 33 | } 34 | 35 | interface Foo1 36 | { 37 | void hello(); 38 | int nothrowMethod() nothrow; 39 | int nogcMethod() @nogc; 40 | nothrow int prefixNothrowMethod(); 41 | @nogc int prefixNogcMethod(); 42 | } 43 | 44 | interface Foo2 45 | { 46 | void world(); 47 | } -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/basic.d.expected: -------------------------------------------------------------------------------- 1 | implement 28 2 | void virtualMethod 3 | // TODO: optional implementation 4 | override int abstractMethod 5 | !package string stringMethod 6 | string stringMethod 7 | Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c) 8 | void normalMethod() 9 | int attributeSuffixMethod() nothrow @property @nogc 10 | !middleprivate1 11 | !middleprivate2 12 | extern (C) @property @nogc ref immutable int attributePrefixMethod() const 13 | !alreadyImplementedMethod 14 | deprecated("foo") void deprecatedMethod() 15 | !staticMethod 16 | protected void protectedMethod() 17 | !void barfoo 18 | void hello() 19 | int nothrowMethod() nothrow 20 | int nogcMethod() @nogc 21 | nothrow int prefixNothrowMethod() 22 | @nogc int prefixNogcMethod() 23 | void world() 24 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/implicit_abstract.d: -------------------------------------------------------------------------------- 1 | module implicit_abstract; 2 | 3 | class Foo : Bar 4 | { 5 | } 6 | 7 | class Bar 8 | { 9 | abstract int prop() @property; 10 | } 11 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/implicit_abstract.d.expected: -------------------------------------------------------------------------------- 1 | implement 40 2 | override int prop() @property -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/nested_preimplemented.d: -------------------------------------------------------------------------------- 1 | module nested_preimplemented; 2 | 3 | class HTMLElement : Element 4 | { 5 | string name() @property 6 | { 7 | return "foo"; 8 | } 9 | } 10 | 11 | abstract class Element : Node 12 | { 13 | string tagName() @property 14 | { 15 | return "x"; 16 | } 17 | 18 | abstract int numAttributes() @property; 19 | 20 | abstract string getAttribute(string name) 21 | { 22 | return name; 23 | } 24 | 25 | final void dontImplement() {} 26 | } 27 | 28 | interface Node 29 | { 30 | void addChild(Node n); 31 | string name() @property; 32 | string tagName() @property; 33 | } 34 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/nested_preimplemented.d.expected: -------------------------------------------------------------------------------- 1 | implement 52 2 | !string name() 3 | !string tagName() 4 | int numAttributes() @property 5 | string getAttribute(string name) 6 | return super.getAttribute(name); 7 | !dontImplement 8 | void addChild(Node n) -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/nested_types.d: -------------------------------------------------------------------------------- 1 | module nested_types; 2 | 3 | class CoolClassImpl : CoolClass 4 | { 5 | 6 | } 7 | 8 | abstract class CoolClass 9 | { 10 | struct Helper 11 | { 12 | int x, y; 13 | 14 | void foo() 15 | { 16 | } 17 | } 18 | 19 | class Helper2 20 | { 21 | string foo; 22 | 23 | void bar(); 24 | } 25 | 26 | void toImplement(); 27 | Helper accessHelper(); 28 | Helper2 accessHelper2(); 29 | } 30 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/nested_types.d.expected: -------------------------------------------------------------------------------- 1 | implement 48 2 | !void foo 3 | !void bar 4 | void toImplement() 5 | CoolClass.Helper accessHelper() 6 | CoolClass.Helper2 accessHelper2() -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/poly.d: -------------------------------------------------------------------------------- 1 | module poly; 2 | 3 | class Foo : IFoo1, IFoo2 4 | { 5 | } 6 | 7 | interface IFoo1 : IFoo 8 | { 9 | void firstFoo(); 10 | } 11 | 12 | interface IFoo2 : IFoo 13 | { 14 | void secondFoo(); 15 | } 16 | 17 | interface IFoo 18 | { 19 | void baseFoo(); 20 | } 21 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/poly.d.expected: -------------------------------------------------------------------------------- 1 | implement 27 - expecting a breadth-first search (for cleaner implementation) 2 | #=1 firstFoo 3 | #=1 secondFoo 4 | #=1 baseFoo 5 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/preimplemented.d: -------------------------------------------------------------------------------- 1 | module preimplemented; 2 | 3 | class Element : Node, Node2 4 | { 5 | void wronglyImplemented() 6 | { 7 | } 8 | 9 | string name() @property 10 | { 11 | return "foo"; 12 | } 13 | } 14 | 15 | interface Node 16 | { 17 | void addChild(Node n); 18 | bool wronglyImplemented(); 19 | string name() @property; 20 | } 21 | 22 | interface Node2 23 | { 24 | void removeChild(Node n); 25 | } 26 | -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/preimplemented.d.expected: -------------------------------------------------------------------------------- 1 | implement 42 2 | void addChild(Node n) 3 | bool wronglyImplemented() 4 | !string name 5 | void removeChild(Node n) -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/properties.d: -------------------------------------------------------------------------------- 1 | module properties; 2 | 3 | class Foo : Bar 4 | { 5 | private int m_tree; 6 | private int mCar; 7 | private int _heli; 8 | private int sharp; 9 | private int java; 10 | } 11 | 12 | interface Bar 13 | { 14 | int tree() @property; 15 | void tree(int value) @property; 16 | int car() @property; 17 | void car(int foobar) @property; 18 | int heli() @property; 19 | void heli(int value) @property; 20 | int Sharp() @property; 21 | void Sharp(int value) @property; 22 | int getJava(); 23 | void setJava(int value); 24 | 25 | int getter() @property; 26 | void setter(int value) @property; 27 | } -------------------------------------------------------------------------------- /test/tc_implement_interface/tests/properties.d.expected: -------------------------------------------------------------------------------- 1 | implement 33 2 | int tree() @property 3 | return m_tree 4 | void tree(int value) @property 5 | m_tree = value; 6 | 7 | int car() @property 8 | return mCar 9 | void car(int foobar) @property 10 | mCar = foobar; 11 | 12 | int heli() @property 13 | return _heli 14 | void heli(int value) @property 15 | _heli = value; 16 | 17 | int Sharp() @property 18 | return sharp 19 | void Sharp(int value) @property 20 | sharp = value; 21 | 22 | int getJava() 23 | return java 24 | void setJava(int value) 25 | java = value; 26 | 27 | int getter() @property 28 | void setter(int value) @property 29 | -------------------------------------------------------------------------------- /test/tc_integrated_dfmt/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dfmt" 2 | 3 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_integrated_dfmt/source/app.d: -------------------------------------------------------------------------------- 1 | import std.file; 2 | import std.string; 3 | 4 | import workspaced.api; 5 | import workspaced.coms; 6 | 7 | void main() 8 | { 9 | string dir = getcwd; 10 | scope backend = new WorkspaceD(); 11 | backend.register!DfmtComponent; 12 | 13 | auto dfmt = backend.get!DfmtComponent; 14 | assert(dfmt.format("void main(){}").getBlocking.splitLines.length > 1); 15 | } 16 | -------------------------------------------------------------------------------- /test/tc_integrated_dscanner/dub.sdl: -------------------------------------------------------------------------------- 1 | name "test-dscanner" 2 | 3 | stringImportPaths "source" 4 | 5 | dependency "serve-d:workspace-d" path="../.." -------------------------------------------------------------------------------- /test/tc_integrated_dscanner/source/app.d: -------------------------------------------------------------------------------- 1 | import std.file; 2 | import std.stdio; 3 | import std.path; 4 | 5 | import workspaced.api; 6 | import workspaced.coms; 7 | import workspaced.com.dscanner; 8 | 9 | enum mainLine = __LINE__ + 1; 10 | void main() 11 | { 12 | string dir = getcwd; 13 | scope backend = new WorkspaceD(); 14 | auto instance = backend.addInstance(dir); 15 | 16 | backend.register!DscannerComponent; 17 | auto dscanner = backend.get!DscannerComponent(dir); 18 | 19 | auto issues = dscanner.lint("", "dscanner.ini", 20 | "void main() { int unused = 0; } void undocumented() { }").getBlocking; 21 | assert(issues.length >= 3); 22 | auto defs = dscanner.listDefinitions("app.d", import("app.d")).getBlocking 23 | .definitions; 24 | assert(defs.length == 2); 25 | assert(defs[0].name == "mainLine"); 26 | assert(defs[0].line == mainLine - 1); 27 | assert(defs[0].type == 'v'); 28 | 29 | assert(defs[1].name == "main"); 30 | assert(defs[1].line == mainLine); 31 | assert(defs[1].type == 'f'); 32 | assert(defs[1].attributes.length >= 1); 33 | assert(defs[1].attributes["signature"] == "()"); 34 | 35 | backend.register!FSWorkspaceComponent; 36 | auto fsworkspace = backend.get!FSWorkspaceComponent(dir); 37 | 38 | fsworkspace.addImports(["source"]); 39 | assert(dscanner.findSymbol("main") 40 | .getBlocking[0] == FileLocation(buildNormalizedPath(dir, "source/app.d"), mainLine, 6)); 41 | } 42 | -------------------------------------------------------------------------------- /views/ddocs.txt: -------------------------------------------------------------------------------- 1 | Authors 2 | Bugs 3 | Date 4 | Deprecated 5 | Examples 6 | History 7 | License 8 | Returns 9 | See_Also 10 | Standards 11 | Throws 12 | Version 13 | Copyright 14 | Params 15 | Macros -------------------------------------------------------------------------------- /workspace-d/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | max_line_length = 170 3 | indent_style = tab 4 | 5 | [*.d] 6 | # allman, otbs or stroustrup - see https://en.wikipedia.org/wiki/Indent_style 7 | dfmt_brace_style = allman 8 | # The formatting process will usually keep lines below this length, but they may be up to max_line_length columns long. 9 | dfmt_soft_max_line_length = 160 10 | # Place operators on the end of the previous line when splitting lines 11 | dfmt_split_operator_at_line_end = false 12 | # Insert space after the closing paren of a cast expression 13 | dfmt_space_after_cast = true 14 | # Insert space after the module name and before the : for selective imports 15 | dfmt_selective_import_space = true 16 | # Place labels on the same line as the labeled switch, for, foreach, or while statement 17 | dfmt_compact_labeled_statements = true 18 | # 19 | # Not yet implemented: 20 | # 21 | # Align labels, cases, and defaults with their enclosing switch 22 | dfmt_align_switch_statements = true 23 | # Decrease the indentation level of attributes 24 | dfmt_outdent_attributes = true 25 | # Insert space after if, while, foreach, etc, and before the ( 26 | dfmt_space_after_keywords = true 27 | -------------------------------------------------------------------------------- /workspace-d/.env.fish: -------------------------------------------------------------------------------- 1 | function d 2 | dub run :dml 3 | if dub build $argv 4 | dub run :test 5 | end 6 | end 7 | 8 | function b 9 | dub run :dml 10 | dub build $argv 11 | mv workspace-d ~/etc-bin/workspace-d 12 | killall dcd-server; killall workspace-d 13 | end 14 | 15 | function r 16 | dub run :dml 17 | dub build --build=release $argv 18 | end 19 | -------------------------------------------------------------------------------- /workspace-d/.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [WebFreak001] 2 | patreon: WebFreak 3 | -------------------------------------------------------------------------------- /workspace-d/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | /util/__test__library__ 7 | /test/workspace-d_test 8 | *.a 9 | /workspace-d 10 | docs/ 11 | bin/ 12 | iworkspaced 13 | workspace-d_dml 14 | debs/ 15 | *.exe 16 | *.dll 17 | workspace-d*.zip 18 | workspace-d*.tar.xz 19 | __test__*__ 20 | *-test-* 21 | .vscode/ 22 | .vs/ 23 | *.lib 24 | -------------------------------------------------------------------------------- /workspace-d/.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | d: 3 | - dmd 4 | - ldc 5 | os: 6 | - linux 7 | - osx 8 | script: dub test --compiler=${DC} && cd test && bash runtests.sh ${DC} -------------------------------------------------------------------------------- /workspace-d/README.md: -------------------------------------------------------------------------------- 1 | # serve-d:workspace-d 2 | 3 | Join the chat: [![Join on Discord](https://discordapp.com/api/guilds/242094594181955585/widget.png?style=shield)](https://discord.gg/Bstj9bx) 4 | 5 | workspace-d wraps dcd, dfmt and dscanner to one unified environment managed by dub. 6 | -------------------------------------------------------------------------------- /workspace-d/dml_gen/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dml", 3 | "targetType": "executable", 4 | "dependencies": { 5 | "pegged": "~>0.3.3", 6 | "dunit": "~>1.0.14" 7 | }, 8 | "buildRequirements": [ 9 | "allowWarnings", 10 | "silenceWarnings" 11 | ] 12 | } -------------------------------------------------------------------------------- /workspace-d/dml_gen/source/app.d: -------------------------------------------------------------------------------- 1 | import std.file; 2 | import std.conv; 3 | import std.string; 4 | 5 | import lookupgen; 6 | 7 | void main() 8 | { 9 | test(); 10 | 11 | string prefix = q{// 12 | // 13 | // DO NOT EDIT 14 | // 15 | // This module has been generated automatically from views/dml-completions.txt using `dub run :dml` 16 | // 17 | // 18 | module workspaced.completion.dml; 19 | 20 | import workspaced.com.dlangui; 21 | 22 | }; 23 | string compStr = "[" ~ generateCompletions(readText("views/dml-completion.txt")) 24 | .to!(string[]).join(",\n\t") ~ "]"; 25 | string completions = "enum dmlCompletions = " ~ compStr ~ ";"; 26 | 27 | write("source/workspaced/completion/dml.d", prefix ~ completions); 28 | } 29 | -------------------------------------------------------------------------------- /workspace-d/dub.sdl: -------------------------------------------------------------------------------- 1 | name "workspace-d" 2 | description "Provides functions for IDEs for managing DCD, Dscanner and Dfmt." 3 | authors "webfreak" 4 | copyright "Copyright © 2017-2023, webfreak" 5 | license "MIT" 6 | 7 | dependency "dfmt" version="~>0.15.0" 8 | dependency "inifiled" version="1.3.3" 9 | dependency "serve-d:dcd" path=".." 10 | dependency "dub" version="~>1.38.0-beta.1" 11 | dependency "emsi_containers" version="0.9.0" 12 | dependency "dscanner" version="~>0.16.0-beta.1" 13 | dependency "libdparse" version="~>0.23.0" 14 | dependency "standardpaths" version="0.8.2" 15 | dependency "mir-algorithm" version="~>3.20" 16 | 17 | configuration "library" { 18 | targetType "library" 19 | } 20 | 21 | configuration "unittest" { 22 | dependency "silly" version="~>1.1.1" 23 | dflags "-checkaction=context" "-allinst" 24 | } 25 | 26 | buildType "unittest-optimized" { 27 | buildOptions "optimize" "releaseMode" "unittests" 28 | } 29 | -------------------------------------------------------------------------------- /workspace-d/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "dcd": "0.16.0-beta.2", 5 | "dfmt": "0.15.1", 6 | "dscanner": "0.16.0-beta.4", 7 | "dub": "1.38.0-beta.1", 8 | "emsi_containers": "0.9.0", 9 | "inifiled": "1.3.3", 10 | "isfreedesktop": "0.1.1", 11 | "libddoc": "0.8.0", 12 | "libdparse": "0.23.2", 13 | "mir-algorithm": "3.22.1", 14 | "mir-core": "1.7.1", 15 | "msgpack-d": "1.0.5", 16 | "serve-d": {"path":".."}, 17 | "silly": "1.1.1", 18 | "standardpaths": "0.8.2", 19 | "xdgpaths": "0.2.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/dcd_version.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.dcd_version; 2 | 3 | // this is a separate file so the download_dcd.d file in tests can import this version 4 | enum latestKnownDCDVersion = [0, 15, 2]; 5 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/dmd.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.dmd; 2 | 3 | import core.thread; 4 | import std.array; 5 | import std.datetime; 6 | import std.datetime.stopwatch : StopWatch; 7 | import std.file; 8 | import std.json; 9 | import std.path; 10 | import std.process; 11 | import std.random; 12 | 13 | import workspaced.api; 14 | 15 | @component("dmd") 16 | class DMDComponent : ComponentWrapper 17 | { 18 | mixin DefaultComponentWrapper; 19 | 20 | /// Tries to compile a snippet of code with the import paths in the current directory. The arguments `-c -o-` are implicit. 21 | /// The sync function may be used to prevent other measures from running while this is running. 22 | /// Params: 23 | /// cb = async callback 24 | /// code = small code snippet to try to compile 25 | /// dmdArguments = additional arguments to pass to dmd before file name 26 | /// count = how often to compile (duration is divided by either this or less in case timeout is reached) 27 | /// timeoutMsecs = when to abort compilation after, note that this will not abort mid-compilation but not do another iteration if this timeout has been reached. 28 | /// Returns: [DMDMeasureReturn] containing logs from only the first compilation pass 29 | Future!DMDMeasureReturn measure(scope const(char)[] code, 30 | string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 31 | { 32 | return typeof(return).async(() => measureSync(code, dmdArguments, count, timeoutMsecs)); 33 | } 34 | 35 | /// ditto 36 | DMDMeasureReturn measureSync(scope const(char)[] code, 37 | string[] dmdArguments = [], int count = 1, int timeoutMsecs = 5000) 38 | { 39 | dmdArguments ~= ["-c", "-o-"]; 40 | DMDMeasureReturn ret; 41 | 42 | auto timeout = timeoutMsecs.msecs; 43 | 44 | StopWatch sw; 45 | 46 | int effective; 47 | 48 | foreach (i; 0 .. count) 49 | { 50 | if (sw.peek >= timeout) 51 | break; 52 | string[] baseArgs = [path]; 53 | foreach (path; importPaths) 54 | baseArgs ~= "-I=" ~ path; 55 | foreach (path; stringImportPaths) 56 | baseArgs ~= "-J=" ~ path; 57 | auto pipes = pipeProcess(baseArgs ~ dmdArguments ~ "-", 58 | Redirect.stderrToStdout | Redirect.stdout | Redirect.stdin, null, 59 | Config.none, refInstance ? refInstance.cwd : getcwd()); 60 | pipes.stdin.write(code); 61 | pipes.stdin.close(); 62 | if (i == 0) 63 | { 64 | if (count == 0) 65 | sw.start(); 66 | ret.log = pipes.stdout.byLineCopy().array; 67 | auto status = pipes.pid.wait(); 68 | if (count == 0) 69 | sw.stop(); 70 | ret.success = status == 0; 71 | ret.crash = status < 0; 72 | } 73 | else 74 | { 75 | if (count < 10 || i != 1) 76 | sw.start(); 77 | pipes.pid.wait(); 78 | if (count < 10 || i != 1) 79 | sw.stop(); 80 | pipes.stdout.close(); 81 | effective++; 82 | } 83 | if (!ret.success) 84 | break; 85 | } 86 | 87 | ret.duration = sw.peek; 88 | 89 | if (effective > 0) 90 | ret.duration = ret.duration / effective; 91 | 92 | return ret; 93 | } 94 | 95 | string path() @property @ignoredFunc const 96 | { 97 | return config.get("dmd", "path", "dmd"); 98 | } 99 | } 100 | 101 | /// 102 | version (DigitalMars) unittest 103 | { 104 | scope backend = new WorkspaceD(); 105 | auto workspace = makeTemporaryTestingWorkspace; 106 | auto instance = backend.addInstance(workspace.directory); 107 | backend.register!DMDComponent; 108 | auto measure = backend.get!DMDComponent(workspace.directory) 109 | .measure("import std.stdio;", null, 100).getBlocking; 110 | assert(measure.success); 111 | assert(measure.duration < 5.seconds); 112 | } 113 | 114 | /// 115 | struct DMDMeasureReturn 116 | { 117 | /// true if dmd returned 0 118 | bool success; 119 | /// true if an ICE occured (segfault / negative return code) 120 | bool crash; 121 | /// compilation output 122 | string[] log; 123 | /// how long compilation took (serialized to msecs float in json) 124 | Duration duration; 125 | } 126 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/fsworkspace.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.fsworkspace; 2 | 3 | import std.json; 4 | import workspaced.api; 5 | 6 | @component("fsworkspace") 7 | @instancedOnly 8 | class FSWorkspaceComponent : ComponentWrapper 9 | { 10 | mixin DefaultComponentWrapper; 11 | 12 | protected void load() 13 | { 14 | if (!refInstance) 15 | throw new Exception("fsworkspace requires to be instanced"); 16 | 17 | paths = instance.cwd ~ config.get!(string[])("fsworkspace", "additionalPaths"); 18 | importPathProvider = &imports; 19 | stringImportPathProvider = &imports; 20 | importFilesProvider = &imports; 21 | } 22 | 23 | /// Adds new import paths to the workspace. You can add import paths, string import paths or file paths. 24 | void addImports(string[] values) 25 | { 26 | paths ~= values; 27 | } 28 | 29 | /// Lists all import-, string import- & file import paths 30 | string[] imports() nothrow 31 | { 32 | return paths; 33 | } 34 | 35 | private: 36 | string[] paths; 37 | } 38 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/references.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.references; 2 | 3 | import workspaced.api; 4 | import workspaced.helpers; 5 | 6 | import workspaced.com.dcd; 7 | import workspaced.com.index; 8 | import workspaced.com.moduleman; 9 | 10 | import std.file; 11 | import std.experimental.logger; 12 | 13 | @component("references") 14 | @instancedOnly 15 | class ReferencesComponent : ComponentWrapper 16 | { 17 | mixin DefaultComponentWrapper; 18 | 19 | protected void load() 20 | { 21 | if (!refInstance) 22 | throw new Exception("references component requires to be instanced"); 23 | } 24 | 25 | /// Basic text-search-based references lookup. 26 | /// Be careful with the delegate, as it is being called from another thread. 27 | /// Do NOT call any other async workspace-d APIs from this callback as it 28 | /// could result in a deadlock. 29 | Future!References findReferences(string file, scope const(char)[] code, int pos, 30 | void delegate(References) asyncFoundPart) 31 | { 32 | auto future = new typeof(return); 33 | auto declTask = get!DCDComponent.findDeclaration(code, pos); 34 | declTask.onDone({ 35 | try 36 | { 37 | References ret; 38 | 39 | auto decl = declTask.getImmediately; 40 | if (decl is DCDDeclaration.init) 41 | return future.finish(References.init); 42 | 43 | if (decl.file == "stdin") 44 | decl.file = file; 45 | 46 | ret.definitionFile = decl.file; 47 | ret.definitionLocation = cast(int)decl.position; 48 | 49 | scope definitionCode = readText(decl.file); 50 | string identifier = getIdentifierAt(definitionCode, decl.position).idup; 51 | string startModule = get!ModulemanComponent.moduleName(definitionCode); 52 | 53 | auto localUseTask = get!DCDComponent.findLocalUse( 54 | definitionCode, ret.definitionLocation); 55 | localUseTask.onDone({ 56 | try 57 | { 58 | auto localUse = localUseTask.getImmediately; 59 | if (localUse.declarationFilePath == "stdin") 60 | localUse.declarationFilePath = ret.definitionFile; 61 | 62 | foreach (use; localUse.uses) 63 | ret.references ~= References.Reference( 64 | localUse.declarationFilePath, cast(int)use); 65 | 66 | asyncFoundPart(ret); 67 | 68 | if (identifier.length) 69 | { 70 | bool[ModuleRef] visited; 71 | visited[startModule] = true; 72 | grepRecursive(ret, 73 | startModule, 74 | identifier, 75 | visited, 76 | asyncFoundPart); 77 | grepIncomplete(ret, 78 | identifier, 79 | visited, 80 | asyncFoundPart); 81 | } 82 | future.finish(ret); 83 | } 84 | catch (Throwable t) 85 | { 86 | future.error(t); 87 | } 88 | }); 89 | } 90 | catch (Throwable t) 91 | { 92 | future.error(t); 93 | } 94 | }); 95 | return future; 96 | } 97 | 98 | private: 99 | void grepRecursive(ref References ret, ModuleRef start, string identifier, 100 | ref bool[ModuleRef] visited, void delegate(References) asyncFoundPart) 101 | { 102 | ModuleRef[] stack = [start]; 103 | stack.reserve(32); 104 | while (stack.length) 105 | { 106 | auto item = stack[$ - 1]; 107 | stack.length--; 108 | 109 | get!IndexComponent.iterateModuleReferences(item, (other) { 110 | if (other in visited) 111 | return; 112 | visited[other] = true; 113 | 114 | auto filename = get!IndexComponent.getIndexedFileName(other); 115 | if (filename.length) 116 | { 117 | scope content = readText(filename); 118 | auto slice = grepFileReferences(ret, content, filename, identifier); 119 | asyncFoundPart(References(null, 0, slice)); 120 | } 121 | else 122 | { 123 | warningf("Failed to find source for module '%s' for find references. (from imports usage)", other); 124 | } 125 | 126 | stack.assumeSafeAppend ~= other; 127 | }); 128 | get!IndexComponent.iteratePublicImports(item, (other) { 129 | if (other in visited) 130 | return; 131 | visited[other] = true; 132 | 133 | auto filename = get!IndexComponent.getIndexedFileName(other); 134 | if (filename.length) 135 | { 136 | scope content = readText(filename); 137 | auto slice = grepFileReferences(ret, content, filename, identifier); 138 | asyncFoundPart(References(null, 0, slice)); 139 | } 140 | else 141 | { 142 | warningf("Failed to find source for module '%s' for find references. (from public imports usage)", other); 143 | } 144 | 145 | stack.assumeSafeAppend ~= other; 146 | }); 147 | } 148 | } 149 | 150 | void grepIncomplete(ref References ret, string identifier, 151 | ref bool[ModuleRef] visited, void delegate(References) asyncFoundPart) 152 | { 153 | get!IndexComponent.iterateIncompleteModules((other) { 154 | if (other in visited) 155 | return; 156 | visited[other] = true; 157 | // ignore incomplete stdlib, hacky but improves performance for now 158 | if (isStdLib(other)) 159 | return; 160 | 161 | auto filename = get!IndexComponent.getIndexedFileName(other); 162 | if (filename.length) 163 | { 164 | scope content = readText(filename); 165 | auto slice = grepFileReferences(ret, content, filename, identifier); 166 | asyncFoundPart(References(null, 0, slice)); 167 | } 168 | else 169 | { 170 | warningf("Failed to find source for module '%s' for find references. (from incomplete/mixin files)", other); 171 | } 172 | 173 | grepRecursive(ret, other, identifier, visited, asyncFoundPart); 174 | }); 175 | } 176 | 177 | static References.Reference[] grepFileReferences(ref References ret, scope const(char)[] code, string file, string identifier) 178 | { 179 | ptrdiff_t i = 0; 180 | size_t start = ret.references.length; 181 | while (true) 182 | { 183 | i = indexOfKeyword(code, identifier, i); 184 | if (i == -1) 185 | break; 186 | ret.references ~= References.Reference(file, cast(int)i); 187 | i++; 188 | } 189 | return ret.references[start .. $]; 190 | } 191 | } 192 | 193 | struct References 194 | { 195 | struct Reference 196 | { 197 | string file; 198 | int location; 199 | } 200 | 201 | string definitionFile; 202 | int definitionLocation; 203 | Reference[] references; 204 | } 205 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/snippets/_package_tests.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.snippets._package_tests; 2 | 3 | import std.conv; 4 | 5 | import workspaced.api; 6 | import workspaced.com.dfmt; 7 | import workspaced.com.snippets; 8 | import workspaced.helpers; 9 | 10 | unittest 11 | { 12 | scope backend = new WorkspaceD(); 13 | auto workspace = makeTemporaryTestingWorkspace; 14 | auto instance = backend.addInstance(workspace.directory); 15 | backend.register!SnippetsComponent; 16 | backend.register!DfmtComponent; 17 | SnippetsComponent snippets = backend.get!SnippetsComponent(workspace.directory); 18 | 19 | auto args = ["--indent_style", "tab"]; 20 | 21 | auto res = snippets.formatSync("void main(${1:string[] args}) {\n\t$0\n}", args); 22 | assert(res == "void main(${1:string[] args})\n{\n\t$0\n}"); 23 | 24 | res = snippets.formatSync("class ${1:MyClass} {\n\t$0\n}", args); 25 | assert(res == "class ${1:MyClass}\n{\n\t$0\n}"); 26 | 27 | res = snippets.formatSync("enum ${1:MyEnum} = $2;\n$0", args); 28 | assert(res == "enum ${1:MyEnum} = $2;\n$0"); 29 | 30 | res = snippets.formatSync("import ${1:std};\n$0", args); 31 | assert(res == "import ${1:std};\n$0"); 32 | 33 | res = snippets.formatSync("import ${1:std};\n$0", args, SnippetLevel.method); 34 | assert(res == "import ${1:std};\n$0"); 35 | 36 | res = snippets.formatSync("foo(delegate() {\n${1:// foo}\n});", args, SnippetLevel.method); 37 | assert(res == "foo(delegate() {\n\t${1:// foo}\n});"); 38 | 39 | res = snippets.formatSync(`auto ${1:window} = new SimpleWindow(Size(${2:800, 600}), "$3");`, args, SnippetLevel.method); 40 | assert(res == `auto ${1:window} = new SimpleWindow(Size(${2:800, 600}), "$3");`); 41 | } 42 | 43 | unittest 44 | { 45 | import workspaced.helpers; 46 | 47 | scope backend = new WorkspaceD(); 48 | auto workspace = makeTemporaryTestingWorkspace; 49 | auto instance = backend.addInstance(workspace.directory); 50 | backend.register!SnippetsComponent; 51 | SnippetsComponent snippets = backend.get!SnippetsComponent(workspace.directory); 52 | 53 | runTestDataFileTests("test/data/snippet_info", null, null, 54 | (code, parts, line) { 55 | assert(parts.length == 2, "malformed snippet info test line: " ~ line); 56 | 57 | auto i = snippets.determineSnippetInfo(null, code, parts[0].to!int); 58 | assert(i.level == parts[1].to!SnippetLevel, i.stack.to!string); 59 | }, null); 60 | } -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/snippets/control_flow.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.snippets.control_flow; 2 | 3 | import workspaced.api; 4 | import workspaced.com.snippets; 5 | 6 | import std.algorithm; 7 | import std.conv; 8 | import std.string; 9 | 10 | class ControlFlowSnippetProvider : SnippetProvider 11 | { 12 | Future!(Snippet[]) provideSnippets(scope const WorkspaceD.Instance instance, 13 | scope const(char)[] file, scope const(char)[] code, int position, const SnippetInfo info) 14 | { 15 | Snippet[] res; 16 | 17 | SnippetLevel lastBreakable = info.findInLocalScope(SnippetLevel.loop, SnippetLevel.switch_); 18 | 19 | if (lastBreakable != SnippetLevel.init) 20 | { 21 | bool isSwitch = lastBreakable == SnippetLevel.switch_; 22 | 23 | { 24 | Snippet snp; 25 | snp.providerId = typeid(this).name; 26 | snp.id = snp.title = snp.shortcut = "break"; 27 | snp.plain = snp.snippet = "break;"; 28 | snp.documentation = isSwitch 29 | ? "break out of the current switch" 30 | : "break out of this loop"; 31 | snp.resolved = true; 32 | snp.unformatted = true; 33 | res ~= snp; 34 | } 35 | 36 | if (isSwitch) 37 | { 38 | { 39 | Snippet snp; 40 | snp.providerId = typeid(this).name; 41 | snp.id = snp.title = snp.shortcut = "goto case"; 42 | snp.plain = snp.snippet = "goto case;"; 43 | snp.documentation = "Explicit fall-through into the next case or to case with explicitly given value.\n\nReference: https://dlang.org/spec/statement.html#goto-statement"; 44 | snp.resolved = true; 45 | snp.unformatted = true; 46 | res ~= snp; 47 | } 48 | { 49 | Snippet snp; 50 | snp.providerId = typeid(this).name; 51 | snp.id = snp.title = snp.shortcut = "goto default"; 52 | snp.plain = snp.snippet = "goto default;"; 53 | snp.documentation = "Go to default case.\n\nReference: https://dlang.org/spec/statement.html#goto-statement"; 54 | snp.resolved = true; 55 | snp.unformatted = true; 56 | res ~= snp; 57 | } 58 | { 59 | Snippet snp; 60 | snp.providerId = typeid(this).name; 61 | snp.id = snp.title = snp.shortcut = "case"; 62 | snp.snippet = "case ${1}:\n\t$0\n\tbreak;"; 63 | snp.documentation = "Defines a case in the current switch-case.\n\nReference: https://dlang.org/spec/statement.html#switch-statement"; 64 | snp.resolved = true; 65 | snp.unformatted = true; 66 | res ~= snp; 67 | } 68 | { 69 | Snippet snp; 70 | snp.providerId = typeid(this).name; 71 | snp.id = snp.title = snp.shortcut = "case range"; 72 | snp.snippet = "case ${1}: .. case ${2}:\n\t$0\n\tbreak;"; 73 | snp.documentation = "Defines a range of cases in the current switch-case, with inclusive start and end.\n\nReference: https://dlang.org/spec/statement.html#switch-statement"; 74 | snp.resolved = true; 75 | snp.unformatted = true; 76 | res ~= snp; 77 | } 78 | { 79 | Snippet snp; 80 | snp.providerId = typeid(this).name; 81 | snp.id = snp.title = snp.shortcut = "default"; 82 | snp.snippet = "default:\n\t$0\n\tbreak;"; 83 | snp.documentation = "Defines the default case in the current switch-case.\n\nReference: https://dlang.org/spec/statement.html#switch-statement"; 84 | snp.resolved = true; 85 | snp.unformatted = true; 86 | res ~= snp; 87 | } 88 | } 89 | else 90 | { 91 | { 92 | Snippet snp; 93 | snp.providerId = typeid(this).name; 94 | snp.id = snp.title = snp.shortcut = "continue"; 95 | snp.plain = snp.snippet = "continue;"; 96 | snp.documentation = "Continue with next iteration"; 97 | snp.resolved = true; 98 | snp.unformatted = true; 99 | res ~= snp; 100 | } 101 | } 102 | } 103 | 104 | return typeof(return).fromResult(res.length ? res : null); 105 | } 106 | 107 | Future!Snippet resolveSnippet(scope const WorkspaceD.Instance instance, 108 | scope const(char)[] file, scope const(char)[] code, int position, 109 | const SnippetInfo info, Snippet snippet) 110 | { 111 | return typeof(return).fromResult(snippet); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/snippets/dependencies.d: -------------------------------------------------------------------------------- 1 | module workspaced.com.snippets.dependencies; 2 | 3 | import workspaced.api; 4 | import workspaced.com.dub; 5 | import workspaced.com.snippets; 6 | 7 | import std.algorithm; 8 | 9 | /// 10 | alias SnippetList = const(PlainSnippet)[]; 11 | 12 | /// A list of dependencies usable in an associative array 13 | struct DependencySet 14 | { 15 | private string[] sorted; 16 | 17 | void set(scope const(string)[] deps) 18 | { 19 | sorted.length = deps.length; 20 | sorted[] = deps; 21 | sorted.sort!"a &v)()); 57 | return ret; 58 | } 59 | } 60 | 61 | /// Representation for a plain snippet with required dependencies. Maps to the 62 | /// parameters of `DependencyBasedSnippetProvider.addSnippet`. 63 | struct DependencySnippet 64 | { 65 | /// 66 | const(string)[] requiredDependencies; 67 | /// 68 | PlainSnippet snippet; 69 | } 70 | 71 | /// ditto 72 | struct DependencySnippets 73 | { 74 | /// 75 | const(string)[] requiredDependencies; 76 | /// 77 | const(PlainSnippet)[] snippets; 78 | } 79 | 80 | class DependencyBasedSnippetProvider : SnippetProvider 81 | { 82 | SnippetList[DependencySet] snippets; 83 | 84 | void addSnippet(const(string)[] requiredDependencies, const PlainSnippet snippet) 85 | { 86 | DependencySet set; 87 | set.set(requiredDependencies); 88 | 89 | if (auto v = set in snippets) 90 | *v ~= snippet; 91 | else 92 | snippets[set] = [snippet]; 93 | } 94 | 95 | Future!(Snippet[]) provideSnippets(scope const WorkspaceD.Instance instance, 96 | scope const(char)[] file, scope const(char)[] code, int position, const SnippetInfo info) 97 | { 98 | if (!instance.has!DubComponent) 99 | return typeof(return).fromResult(null); 100 | else 101 | { 102 | string id = typeid(this).name; 103 | auto dub = instance.get!DubComponent; 104 | return typeof(return).async(delegate() { 105 | // TODO: this should use dependencies, not selections, but this 106 | // regressed: https://github.com/dlang/dub/issues/2853 107 | string[] deps = dub.selectedVersions.keys; 108 | Snippet[] ret; 109 | foreach (k, v; snippets) 110 | { 111 | if (k.hasAll(deps)) 112 | { 113 | foreach (snip; v) 114 | if (snip.levels.canFind(info.level)) 115 | ret ~= snip.buildSnippet(id); 116 | } 117 | } 118 | return ret; 119 | }); 120 | } 121 | } 122 | 123 | Future!Snippet resolveSnippet(scope const WorkspaceD.Instance instance, 124 | scope const(char)[] file, scope const(char)[] code, int position, 125 | const SnippetInfo info, Snippet snippet) 126 | { 127 | snippet.resolved = true; 128 | return typeof(return).fromResult(snippet); 129 | } 130 | } 131 | 132 | unittest 133 | { 134 | DependencySet set; 135 | set.set(["vibe-d", "mir", "serve-d"]); 136 | assert(set.hasAll(["vibe-d", "serve-d", "mir"])); 137 | assert(set.hasAll(["vibe-d", "serve-d", "serve-d", "serve-d", "mir", "mir"])); 138 | assert(set.hasAll(["vibe-d", "serve-d", "mir", "workspace-d"])); 139 | assert(set.hasAll(["diet-ng", "vibe-d", "serve-d", "mir", "workspace-d"])); 140 | assert(!set.hasAll(["diet-ng", "serve-d", "mir", "workspace-d"])); 141 | assert(!set.hasAll(["diet-ng", "serve-d", "vibe-d", "workspace-d"])); 142 | assert(!set.hasAll(["diet-ng", "mir", "mir", "vibe-d", "workspace-d"])); 143 | assert(!set.hasAll(["diet-ng", "mir", "vibe-d", "workspace-d"])); 144 | 145 | set.set(["vibe-d:http"]); 146 | assert(set.hasAll([ 147 | "botan", "botan", "botan-math", "botan-math", "diet-ng", "diet-ng", 148 | "eventcore", "eventcore", "libasync", "libasync", "memutils", 149 | "memutils", "memutils", "mir-linux-kernel", "mir-linux-kernel", 150 | "openssl", "openssl", "openssl", "stdx-allocator", "stdx-allocator", 151 | "stdx-allocator", "taggedalgebraic", "taggedalgebraic", "vibe-core", 152 | "vibe-core", "vibe-d", "vibe-d:core", "vibe-d:core", "vibe-d:core", 153 | "vibe-d:core", "vibe-d:core", "vibe-d:core", "vibe-d:crypto", 154 | "vibe-d:crypto", "vibe-d:crypto", "vibe-d:data", "vibe-d:data", 155 | "vibe-d:data", "vibe-d:http", "vibe-d:http", "vibe-d:http", "vibe-d:http", 156 | "vibe-d:http", "vibe-d:inet", "vibe-d:inet", "vibe-d:inet", "vibe-d:inet", 157 | "vibe-d:mail", "vibe-d:mail", "vibe-d:mongodb", "vibe-d:mongodb", 158 | "vibe-d:redis", "vibe-d:redis", "vibe-d:stream", "vibe-d:stream", 159 | "vibe-d:stream", "vibe-d:stream", "vibe-d:textfilter", "vibe-d:textfilter", 160 | "vibe-d:textfilter", "vibe-d:textfilter", "vibe-d:tls", "vibe-d:tls", 161 | "vibe-d:tls", "vibe-d:tls", "vibe-d:utils", "vibe-d:utils", "vibe-d:utils", 162 | "vibe-d:utils", "vibe-d:utils", "vibe-d:utils", "vibe-d:web", "vibe-d:web" 163 | ])); 164 | 165 | set.set(null); 166 | assert(set.hasAll([])); 167 | assert(set.hasAll(["foo"])); 168 | } 169 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/com/snippets/external_builtin.d: -------------------------------------------------------------------------------- 1 | /// List of dependency-based snippets for packages outside phobos and druntime. 2 | module workspaced.com.snippets.external_builtin; 3 | 4 | import workspaced.com.snippets; 5 | 6 | static immutable PlainSnippet[] builtinVibeHttpSnippets = [ 7 | { 8 | levels: [SnippetLevel.method], 9 | shortcut: "viberouter", 10 | title: "vibe.d router", 11 | documentation: "Basic router instance code with GET / path.\n\nReference: https://vibed.org/api/vibe.http.router/URLRouter", 12 | snippet: "auto ${1:router} = new URLRouter();\n${1:router}.get(\"/\", &${2:index});", 13 | imports: ["vibe.http.router"] 14 | }, 15 | { 16 | levels: [SnippetLevel.method], 17 | shortcut: "vibeserver", 18 | title: "vibe.d HTTP server", 19 | documentation: "Basic vibe.d HTTP server startup code.\n\nReference: https://vibed.org/api/vibe.http.server/", 20 | snippet: "auto ${3:settings} = new HTTPServerSettings();\n" 21 | ~ "${3:settings}.port = ${1:3000};\n" 22 | ~ "${3:settings}.bindAddresses = ${2:[\"::1\", \"127.0.0.1\"]};\n" 23 | ~ "\n" 24 | ~ "auto ${4:router} = new URLRouter();\n" 25 | ~ "${4:router}.get(\"/\", &${5:index});\n" 26 | ~ "\n" 27 | ~ "listenHTTP(${3:settings}, ${4:router});\n", 28 | imports: ["vibe.http.server", "vibe.http.router"] 29 | }, 30 | { 31 | levels: [SnippetLevel.method], 32 | shortcut: "vibeget", 33 | title: "vibe.d GET request", 34 | documentation: "Code for a simple low-level async GET request.\n\nReference: https://vibed.org/api/vibe.http.client/requestHTTP", 35 | snippet: "requestHTTP(URL(\"$1\"), null, (scope HTTPClientResponse res) {\n" 36 | ~ "\t${2:// TODO: check res.statusCode and read response into parent scope variables.}\n" 37 | ~ "});", 38 | imports: ["vibe.http.client"] 39 | }, 40 | { 41 | levels: [SnippetLevel.method], 42 | shortcut: "viberequest", 43 | title: "vibe.d HTTP request (POST/GET/PUT/...)", 44 | documentation: "Code for a simple low-level async HTTP request.\n\nReference: https://vibed.org/api/vibe.http.client/requestHTTP", 45 | snippet: "requestHTTP(URL(\"$1\"), (scope HTTPClientRequest req) {\n" 46 | ~ "\treq.method = HTTPMethod.${2:POST};\n" 47 | ~ "\t${3:// TODO: write request body}\n" 48 | ~ "}, (scope HTTPClientResponse res) {\n" 49 | ~ "\t${4:// TODO: check res.statusCode and read response into parent scope variables.}\n" 50 | ~ "});", 51 | imports: ["vibe.http.client"] 52 | }, 53 | { 54 | levels: [SnippetLevel.method], 55 | shortcut: "vibegetstring", 56 | title: "vibe.d GET request into string", 57 | documentation: "Code for a simple async GET request storing the full response body in a string.\n\nReference: https://vibed.org/api/vibe.http.client/requestHTTP", 58 | snippet: "string ${1:text};\n" 59 | ~ "requestHTTP(URL(\"$2\"), null, (scope HTTPClientResponse res) {\n" 60 | ~ "\t${3:// TODO: check res.statusCode}\n" 61 | ~ "\t${1:text} = res.bodyReader.readAllUTF8();\n" 62 | ~ "});", 63 | imports: ["vibe.http.client"] 64 | }, 65 | { 66 | levels: [SnippetLevel.method], 67 | shortcut: "vibegetjson", 68 | title: "vibe.d GET request as json", 69 | documentation: "Code for a simple async GET request storing the full response body in a string.\n\nReference: https://vibed.org/api/vibe.http.client/requestHTTP", 70 | snippet: "Json ${1:json};\n" 71 | ~ "requestHTTP(URL(\"$2\"), null, (scope HTTPClientResponse res) {\n" 72 | ~ "\t${3:// TODO: check res.statusCode}\n" 73 | ~ "\t${1:json} = res.readJson(); // TODO: possibly want to add .deserializeJson!T\n" 74 | ~ "});", 75 | imports: ["vibe.data.json", "vibe.http.client"] 76 | }, 77 | ]; 78 | 79 | static immutable PlainSnippet[] builtinMirSerdeSnippets = [ 80 | { 81 | levels: [SnippetLevel.type, SnippetLevel.mixinTemplate], 82 | shortcut: "deserializeFromIon", 83 | title: "mir-ion deserializeFromIon", 84 | documentation: "Custom mir-ion struct deserializion code.\n\n" 85 | ~ "**Note:** this is an advanced construct and you probably don't " 86 | ~ "need to use this unless you have very specific needs. You can " 87 | ~ "probably use a proxy instead.", 88 | snippet: "@safe pure scope\n" 89 | ~ "IonException deserializeFromIon(scope const char[][] symbolTable, IonDescribedValue value) {\n" 90 | ~ "\timport mir.deser.ion : deserializeIon;\n" 91 | ~ "\timport mir.ion.type_code : IonTypeCode;\n" 92 | ~ "\n" 93 | ~ "\tif (value.descriptor.type == IonTypeCode.struct_) {\n" 94 | ~ "\t\t${1:this.impl} = deserializeIon!${2:DeserializeType}(symbolTable, value);$0\n" 95 | ~ "\t} else {\n" 96 | ~ "\t\treturn ionException(IonErrorCode.expectedStructValue);\n" 97 | ~ "\t}\n" 98 | ~ "\treturn null;\n" 99 | ~ "}\n", 100 | imports: ["mir.ion.exception", "mir.ion.value"] 101 | }, 102 | { 103 | levels: [SnippetLevel.type, SnippetLevel.mixinTemplate], 104 | shortcut: "serializeIon", 105 | title: "mir-ion serialize", 106 | documentation: "Custom mir-ion struct serializion code.\n\n" 107 | ~ "**Note:** a proxy might achieve the same thing if you just want to " 108 | ~ "serialize a single member.", 109 | snippet: "void serialize(S)(scope ref S serializer) const scope {\n" 110 | ~ "\timport mir.ser : serializeValue;\n" 111 | ~ "\n" 112 | ~ "\tserializeValue(serializer, ${1:this.impl});$0\n" 113 | ~ "}\n" 114 | }, 115 | ]; 116 | 117 | static immutable DependencySnippets[] builtinDependencySnippets = [ 118 | DependencySnippets(["vibe-d:http"], builtinVibeHttpSnippets), 119 | DependencySnippets(["mir-ion"], builtinMirSerdeSnippets), 120 | ]; 121 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/coms.d: -------------------------------------------------------------------------------- 1 | module workspaced.coms; 2 | 3 | public import workspaced.com.ccdb : ClangCompilationDatabaseComponent; 4 | public import workspaced.com.dcd : DCDComponent; 5 | public import workspaced.com.dcdext : DCDExtComponent; 6 | public import workspaced.com.dfmt : DfmtComponent; 7 | public import workspaced.com.dlangui : DlanguiComponent; 8 | public import workspaced.com.dmd : DMDComponent; 9 | public import workspaced.com.dscanner : DscannerComponent; 10 | public import workspaced.com.dub : DubComponent; 11 | public import workspaced.com.fsworkspace : FSWorkspaceComponent; 12 | public import workspaced.com.importer : ImporterComponent; 13 | public import workspaced.com.index : IndexComponent; 14 | public import workspaced.com.moduleman : ModulemanComponent; 15 | public import workspaced.com.references : ReferencesComponent; 16 | public import workspaced.com.snippets : SnippetsComponent; 17 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/dub/diagnostics.d: -------------------------------------------------------------------------------- 1 | module workspaced.dub.diagnostics; 2 | 3 | import workspaced.api; 4 | 5 | import std.algorithm; 6 | import std.string; 7 | 8 | import dparse.ast; 9 | import dparse.lexer; 10 | import dparse.parser; 11 | import dparse.rollback_allocator; 12 | 13 | int[2] resolveDubDiagnosticRange(scope const(char)[] code, 14 | scope const(Token)[] tokens, Module parsed, int position, 15 | scope const(char)[] diagnostic) 16 | { 17 | if (diagnostic.startsWith("use `is` instead of `==`", 18 | "use `!is` instead of `!=`")) 19 | { 20 | auto expr = new EqualComparisionFinder(position); 21 | expr.visit(parsed); 22 | if (expr.result !is null) 23 | { 24 | const left = &expr.result.left.tokens[$ - 1]; 25 | const right = &expr.result.right.tokens[0]; 26 | auto between = left[1 .. right - left]; 27 | const tok = between[0]; 28 | if (tok.type == expr.result.operator) 29 | { 30 | auto index = cast(int) tok.index; 31 | return [index, index + 2]; 32 | } 33 | } 34 | } 35 | return [position, position]; 36 | } 37 | 38 | /// Finds the equals comparision at the given index. 39 | /// Used to resolve issue locations for diagnostics of type 40 | /// - use `is` instead of `==` 41 | /// - use `!is` instead of `!=` 42 | class EqualComparisionFinder : ASTVisitor 43 | { 44 | this(size_t index) 45 | { 46 | this.index = index; 47 | } 48 | 49 | override void visit(const(CmpExpression) expr) 50 | { 51 | if (expr.equalExpression !is null) 52 | { 53 | const start = expr.tokens[0].index; 54 | const last = expr.tokens[$ - 1]; 55 | const end = last.index + last.text.length; 56 | if (index >= start && index < end) 57 | { 58 | result = cast(EqualExpression) expr.equalExpression; 59 | } 60 | } 61 | super.visit(expr); 62 | } 63 | 64 | alias visit = ASTVisitor.visit; 65 | size_t index; 66 | EqualExpression result; 67 | } 68 | 69 | unittest 70 | { 71 | string code = q{void main() { 72 | if (foo(a == 4) == null) 73 | { 74 | } 75 | }}.replace("\r\n", "\n"); 76 | 77 | LexerConfig config; 78 | RollbackAllocator rba; 79 | StringCache cache = StringCache(64); 80 | auto tokens = getTokensForParser(cast(ubyte[]) code, config, &cache); 81 | auto parsed = parseModule(tokens, "equal_finder.d", &rba); 82 | 83 | auto range = resolveDubDiagnosticRange(code, tokens, parsed, 19, 84 | "use `is` instead of `==` when comparing with `null`"); 85 | 86 | assert(range == [31, 33]); 87 | } 88 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/dub/lintgenerator.d: -------------------------------------------------------------------------------- 1 | /** 2 | Generator for direct compiler builds. 3 | 4 | Copyright: © 2013-2013 rejectedsoftware e.K. 5 | License: Subject to the terms of the MIT license. 6 | Authors: Sönke Ludwig, Jan Jurzitza 7 | */ 8 | module workspaced.dub.lintgenerator; 9 | 10 | // TODO: this sucks, this is copy pasted from build.d in dub and only removed binary output here 11 | 12 | import dub.compilers.compiler; 13 | import dub.compilers.utils; 14 | import dub.generators.generator; 15 | import dub.internal.utils; 16 | import dub.internal.vibecompat.inet.path; 17 | import dub.package_; 18 | import dub.packagemanager; 19 | import dub.project; 20 | 21 | import std.algorithm; 22 | import std.array; 23 | import std.conv; 24 | import std.exception; 25 | import std.experimental.logger; 26 | import std.file; 27 | import std.process; 28 | import std.string; 29 | 30 | class DubLintGenerator : ProjectGenerator 31 | { 32 | private 33 | { 34 | NativePath m_cwd; 35 | } 36 | 37 | this(Project project, NativePath cwd) 38 | { 39 | super(project); 40 | m_cwd = cwd; 41 | } 42 | 43 | override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) 44 | { 45 | auto root_ti = targets[m_project.rootPackage.name]; 46 | 47 | tracef("Performing \"%s\" build using %s for %-(%s, %).", settings.buildType, 48 | settings.platform.compilerBinary, settings.platform.architecture); 49 | 50 | auto bs = root_ti.buildSettings.dup; 51 | performDirectBuild(settings, bs, root_ti.pack, root_ti.config); 52 | } 53 | 54 | private void performDirectBuild(GeneratorSettings settings, 55 | ref BuildSettings buildsettings, in Package pack, string config) 56 | { 57 | tracef("%s %s: building configuration %s", pack.name, pack.version_, config); 58 | 59 | // make all target/import paths relative 60 | string makeRelative(string path) 61 | { 62 | return shrinkPath(NativePath(path), m_cwd); 63 | } 64 | 65 | buildsettings.targetPath = makeRelative(buildsettings.targetPath); 66 | foreach (ref p; buildsettings.sourceFiles) 67 | p = makeRelative(p); 68 | foreach (ref p; buildsettings.importPaths) 69 | p = makeRelative(p); 70 | foreach (ref p; buildsettings.stringImportPaths) 71 | p = makeRelative(p); 72 | 73 | buildWithCompiler(settings, buildsettings); 74 | } 75 | 76 | private void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings) 77 | { 78 | scope (failure) 79 | { 80 | tracef("FAIL compiler=%s, targetPath=%s, targetName=%s, targetType=%s", 81 | settings.platform.compilerBinary, buildsettings.targetPath, 82 | buildsettings.targetName, buildsettings.targetType); 83 | } 84 | 85 | buildsettings.libs = null; 86 | buildsettings.lflags = null; 87 | buildsettings.addOptions(BuildOption.syntaxOnly); 88 | buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array; 89 | 90 | settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); 91 | 92 | static if (is(typeof(settings.toolWorkingDirectory))) 93 | settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback, settings.toolWorkingDirectory); 94 | else 95 | settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); 96 | } 97 | } 98 | 99 | private string shrinkPath(NativePath path, NativePath base) 100 | { 101 | auto orig = path.toNativeString(); 102 | if (!path.absolute) 103 | return orig; 104 | auto ret = path.relativeTo(base).toNativeString(); 105 | return ret.length < orig.length ? ret : orig; 106 | } 107 | -------------------------------------------------------------------------------- /workspace-d/source/workspaced/future.d: -------------------------------------------------------------------------------- 1 | module workspaced.future; 2 | 3 | import core.time; 4 | 5 | import std.algorithm; 6 | import std.parallelism; 7 | import std.traits : isCallable, isCopyable; 8 | 9 | class Future(T) 10 | { 11 | import core.thread : Fiber, Thread; 12 | 13 | static if (!is(T == void)) 14 | private T value; 15 | Throwable exception; 16 | bool has; 17 | void delegate() _onDone; 18 | private Thread _worker; 19 | 20 | /// Sets the onDone callback if no value has been set yet or calls immediately if the value has already been set or was set during setting the callback. 21 | /// Crashes with an assert error if attempting to override an existing callback (i.e. calling this function on the same object twice). 22 | void onDone(void delegate() callback) @property 23 | { 24 | assert(!_onDone); 25 | if (has) 26 | callback(); 27 | else 28 | { 29 | bool called; 30 | _onDone = { called = true; callback(); }; 31 | if (has && !called) 32 | callback(); 33 | } 34 | } 35 | 36 | static if (is(T == void)) 37 | static Future!void finished() 38 | { 39 | auto ret = new typeof(return); 40 | ret.has = true; 41 | return ret; 42 | } 43 | else 44 | static Future!T fromResult(T value) 45 | { 46 | auto ret = new typeof(return); 47 | ret.value = move(value); 48 | ret.has = true; 49 | return ret; 50 | } 51 | 52 | static Future!T async(T delegate() cb) 53 | { 54 | auto ret = new typeof(return); 55 | ret._worker = new Thread({ 56 | try 57 | { 58 | static if (is(T == void)) 59 | { 60 | cb(); 61 | ret.finish(); 62 | } 63 | else 64 | ret.finish(cb()); 65 | } 66 | catch (Throwable t) 67 | { 68 | ret.error(t); 69 | } 70 | }).start(); 71 | return ret; 72 | } 73 | 74 | static Future!T fromError(Throwable error) 75 | { 76 | auto ret = new typeof(return); 77 | ret.error = error; 78 | ret.has = true; 79 | return ret; 80 | } 81 | 82 | static if (is(T == void)) 83 | void finish() 84 | { 85 | assert(!has); 86 | has = true; 87 | if (_onDone) 88 | _onDone(); 89 | } 90 | else 91 | void finish(T value) 92 | { 93 | assert(!has); 94 | this.value = move(value); 95 | has = true; 96 | if (_onDone) 97 | _onDone(); 98 | } 99 | 100 | void error(Throwable t) 101 | { 102 | assert(!has); 103 | exception = t; 104 | has = true; 105 | if (_onDone) 106 | _onDone(); 107 | } 108 | 109 | /// Waits for the result of this future using Thread.sleep 110 | T getBlockingImpl(bool moveValue, alias sleepDur = 1.msecs)() 111 | { 112 | while (!has) 113 | Thread.sleep(sleepDur); 114 | if (_worker) 115 | { 116 | _worker.join(); 117 | _worker = null; 118 | } 119 | if (exception) 120 | throw exception; 121 | static if (!is(T == void)) 122 | { 123 | static if (moveValue) 124 | return move(value); 125 | else 126 | return value; 127 | } 128 | } 129 | 130 | /// ditto 131 | static if (is(T == void) || isCopyable!T) 132 | alias getBlocking(alias sleepDur = 1.msecs) = getBlockingImpl!(false, sleepDur); 133 | 134 | /// ditto 135 | static if (!is(T == void)) 136 | alias moveBlocking(alias sleepDur = 1.msecs) = getBlockingImpl!(true, sleepDur); 137 | 138 | /// Waits for the result of this future using Fiber.yield 139 | T getYieldImpl(bool moveValue)() 140 | { 141 | assert(Fiber.getThis() !is null, 142 | "Attempted to getYield without being in a Fiber context"); 143 | 144 | while (!has) 145 | Fiber.yield(); 146 | if (_worker) 147 | { 148 | _worker.join(); 149 | _worker = null; 150 | } 151 | if (exception) 152 | throw exception; 153 | static if (!is(T == void)) 154 | { 155 | static if (moveValue) 156 | return move(value); 157 | else 158 | return value; 159 | } 160 | } 161 | 162 | /// ditto 163 | static if (is(T == void) || isCopyable!T) 164 | alias getYield = getYieldImpl!false; 165 | 166 | /// ditto 167 | static if (!is(T == void)) 168 | alias moveYield = getYieldImpl!true; 169 | 170 | /// Waits for the result of this future using Fiber.yield 171 | T getImmediatelyImpl(bool moveValue)() 172 | { 173 | assert(has, 174 | "Attempted to getImmediately without data being ready"); 175 | 176 | if (_worker) 177 | { 178 | _worker.join(); 179 | _worker = null; 180 | } 181 | if (exception) 182 | throw exception; 183 | static if (!is(T == void)) 184 | { 185 | static if (moveValue) 186 | return move(value); 187 | else 188 | return value; 189 | } 190 | } 191 | 192 | /// ditto 193 | static if (is(T == void) || isCopyable!T) 194 | alias getImmediately = getImmediatelyImpl!false; 195 | 196 | /// ditto 197 | static if (!is(T == void)) 198 | alias moveImmediately = getImmediatelyImpl!true; 199 | } 200 | 201 | static void whenAllDone(T)(Future!T[] futs, void delegate() callback) 202 | { 203 | if (!futs.length) 204 | return callback(); 205 | 206 | size_t i = 0; 207 | void onDone() 208 | { 209 | while (i < futs.length && futs[i].has) 210 | i++; 211 | 212 | if (i == futs.length) 213 | callback(); 214 | else 215 | futs[i].onDone(&onDone); 216 | } 217 | futs[0].onDone(&onDone); 218 | } 219 | 220 | enum string gthreadsAsyncProxy(string call) = `auto __futureRet = new typeof(return); 221 | gthreads.create({ 222 | mixin(traceTask); 223 | try 224 | { 225 | __futureRet.finish(` ~ call ~ `); 226 | } 227 | catch (Throwable t) 228 | { 229 | __futureRet.error(t); 230 | } 231 | }); 232 | return __futureRet; 233 | `; 234 | 235 | void create(T)(TaskPool pool, T fun) if (isCallable!T) 236 | { 237 | pool.put(task(fun)); 238 | } 239 | --------------------------------------------------------------------------------