├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yaml │ ├── changelog.yml │ ├── check.yaml │ └── release.yaml ├── .gitignore ├── .luacheckrc ├── .stylua.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── aur ├── PKGBUILD └── PKGBUILD-GIT ├── build.rs ├── docs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── SECURITY.md ├── assets │ ├── readme-screenshot-2.png │ ├── readme-screenshot-3.png │ ├── readme-screenshot.png │ ├── rustowl-logo-dark.svg │ ├── rustowl-logo.svg │ ├── vs-code-cursor-on-unwrap-visualized.png │ ├── vs-code-cursor-on-unwrap.png │ └── vs-code-progress.png ├── installation.md ├── lsp-spec.md └── usage.md ├── ftplugin └── rust.lua ├── lua └── rustowl │ ├── config.lua │ ├── highlight.lua │ ├── init.lua │ ├── lsp.lua │ └── show-on-hover.lua ├── rust-toolchain.toml ├── rustowl.el ├── scripts └── bump.sh ├── src ├── bin │ ├── core │ │ ├── analyze.rs │ │ └── mod.rs │ ├── rustowl.rs │ └── rustowlc.rs ├── cli.rs ├── lib.rs ├── lsp.rs ├── lsp │ ├── backend.rs │ ├── decoration.rs │ └── progress.rs ├── models.rs ├── shells.rs ├── toolchain.rs └── utils.rs └── vscode ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc ├── LICENSE ├── README.md ├── esbuild.js ├── eslint.config.mjs ├── package.json ├── rustowl-icon.png ├── src ├── bootstrap.ts ├── extension.ts └── schemas.ts ├── tsconfig.json └── yarn.lock /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ if .Versions -}} 2 | 3 | ## [Unreleased] 4 | 5 | {{ if .Unreleased.CommitGroups -}} 6 | {{ range .Unreleased.CommitGroups -}} 7 | ### {{ .Title }} 8 | {{ range .Commits -}} 9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | {{ end -}} 13 | {{ end -}} 14 | 15 | {{ range .Versions }} 16 | 17 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} 18 | {{ range .CommitGroups -}} 19 | ### {{ .Title }} 20 | {{ range .Commits -}} 21 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 22 | {{ end }} 23 | {{ end -}} 24 | 25 | {{- if .RevertCommits -}} 26 | ### Reverts 27 | {{ range .RevertCommits -}} 28 | - {{ .Revert.Header }} 29 | {{ end }} 30 | {{ end -}} 31 | 32 | {{- if .MergeCommits -}} 33 | ### Pull Requests 34 | {{ range .MergeCommits -}} 35 | - {{ .Header }} 36 | {{ end }} 37 | {{ end -}} 38 | 39 | {{- if .NoteGroups -}} 40 | {{ range .NoteGroups -}} 41 | ### {{ .Title }} 42 | {{ range .Notes }} 43 | {{ .Body }} 44 | {{ end }} 45 | {{ end -}} 46 | {{ end -}} 47 | {{ end -}} 48 | 49 | {{- if .Versions }} 50 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD 51 | {{ range .Versions -}} 52 | {{ if .Tag.Previous -}} 53 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} 54 | {{ end -}} 55 | {{ end -}} 56 | {{ end -}} 57 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/cordx56/rustowl 6 | options: 7 | commits: 8 | filters: 9 | Type: 10 | - feat 11 | - fix 12 | - perf 13 | - refactor 14 | - chore 15 | commit_groups: 16 | title_maps: 17 | feat: 🚀 Features 18 | fix: 🐞 Bug Fixes 19 | perf: ⚡ Performance Improvements 20 | refactor: ♻️ Code Refactoring 21 | chore: 🎨 Chores 22 | header: 23 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 24 | pattern_maps: 25 | - Type 26 | - Scope 27 | - Subject 28 | notes: 29 | keywords: 30 | - 🚨 Breaking Changes 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 2 | - cordx56 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug to help us improve RustOwl 3 | labels: bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thank you for reporting a bug! 9 | 10 | Before submitting, please search existing issues to avoid duplicates. If a similar issue exists, comment there instead of opening a new one. 11 | 12 | To help us resolve the issue efficiently, please provide the necessary details below. 13 | 14 | - type: textarea 15 | id: bug-description 16 | attributes: 17 | label: Bug Description 18 | description: Describe the issue you encountered. 19 | placeholder: Provide a clear and concise description of the problem, including what you expected to happen and what actually occurred. 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: environment 25 | attributes: 26 | label: Environment 27 | description: Provide details about your setup. 28 | placeholder: | 29 | - OS: [e.g., Windows 11, macOS Sequoia 15.2, Ubuntu 24.04] 30 | - rustc Version: [e.g., 1.84.1 (e71f9a9a9 2025-01-27)] 31 | - rustup Version: [e.g., 1.27.1 (54dd3d00f 2024-04-24)] 32 | - RustOwl Version: [e.g., v0.1.1] 33 | - Editor: [e.g., VSCode 1.97, Neovim, Emacs] 34 | validations: 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for RustOwl 3 | labels: enhancement 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: What is the problem you're trying to solve 8 | description: | 9 | A clear and concise description of what the problem is. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: Describe the solution you'd like 16 | description: | 17 | A clear and concise description of what you'd like to happen. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: Additional context 24 | description: | 25 | Add any other context about the feature request here. 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Related Issue(s) 2 | 3 | 7 | 8 | ## Description 9 | 10 | 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: github-actions 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: npm 12 | directory: "/vscode" 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build RustOwl 2 | 3 | on: 4 | workflow_call: 5 | outputs: 6 | run_id: 7 | description: Run ID of this workflow 8 | value: ${{ github.run_id }} 9 | 10 | jobs: 11 | check: 12 | uses: ./.github/workflows/check.yaml 13 | 14 | rustowl: 15 | needs: 16 | - check 17 | strategy: 18 | matrix: 19 | os: 20 | - ubuntu-24.04 21 | - ubuntu-24.04-arm 22 | - macos-15 23 | - macos-13 24 | - windows-2022 25 | - windows-11-arm 26 | 27 | runs-on: ${{ matrix.os }} 28 | permissions: 29 | contents: write 30 | defaults: 31 | run: 32 | shell: bash 33 | env: 34 | RUSTUP_TOOLCHAIN: 1.87.0 35 | RUSTC_BOOTSTRAP: rustowlc 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | # Using fat LTO causes failure to link on Windows ARM 42 | - name: Set build profile 43 | run: | 44 | if [[ "${{ matrix.os }}" == "windows-11-arm" ]]; then 45 | echo "build_profile=arm-windows-release" >> $GITHUB_ENV 46 | else 47 | echo "build_profile=release" >> $GITHUB_ENV 48 | fi 49 | 50 | # We don't have rustup on the Windows 11 Arm64 runner yet, so manually installing it. 51 | - if: ${{ matrix.os == 'windows-11-arm' }} 52 | name: Install rustup 53 | run: | 54 | URL='https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe' 55 | HASH="$(curl -sSL "${URL}.sha256")" 56 | curl -sSL -o ./rustup-init.exe "${URL}" 57 | if [ "$(sha256sum ./rustup-init.exe | cut -d' ' -f 1)" = "${HASH}" ]; then 58 | ./rustup-init.exe -y --no-update-default-toolchain 59 | echo "${USERPROFILE}\.cargo\bin" >> "${GITHUB_PATH}" 60 | else 61 | echo "::error::The SHA256 digest does not match." 62 | exit 1 63 | fi 64 | 65 | - name: Setup Rust 66 | run: | 67 | rustup install --profile minimal 68 | rustup component add rust-src rustc-dev llvm-tools 69 | 70 | - name: setup env 71 | run: | 72 | echo "host_tuple=$(rustc --print=host-tuple)" >> $GITHUB_ENV 73 | rustup show active-toolchain | awk '{ print "active_toolchain=" $1 }' >> $GITHUB_ENV 74 | ([[ "$(rustc --print=host-tuple)" == *msvc* ]] && echo "exec_ext=.exe" || echo "exec_ext=") >> $GITHUB_ENV 75 | ([[ "$(rustc --print=host-tuple)" == *windows* ]] && echo "is_windows=true" || echo "is_windows=false") >> $GITHUB_ENV 76 | ([[ "$(rustc --print=host-tuple)" == *linux* ]] && echo "is_linux=true" || echo "is_linux=false") >> $GITHUB_ENV 77 | 78 | - name: Install zig 79 | if: env.is_linux == 'true' 80 | uses: mlugg/setup-zig@v2 81 | with: 82 | version: 0.13.0 83 | 84 | - name: Build 85 | run: | 86 | if [[ "${{ env.is_linux }}" == "true" ]]; then 87 | cargo install --locked cargo-zigbuild 88 | cargo zigbuild --target ${{ env.host_tuple }}.2.17 --profile=${{ env.build_profile }} 89 | else 90 | cargo build --profile=${{ env.build_profile }} 91 | fi 92 | 93 | - name: Set archive name 94 | run: | 95 | if [[ "${{ env.is_windows }}" == "true" ]]; then 96 | echo "archive_name=rustowl-${{ env.host_tuple }}.zip" >> $GITHUB_ENV 97 | else 98 | echo "archive_name=rustowl-${{ env.host_tuple }}.tar.gz" >> $GITHUB_ENV 99 | fi 100 | 101 | - name: Setup archive artifacts 102 | run: | 103 | SYSROOT_PATH=sysroot/${{ env.active_toolchain }} 104 | rm -rf runtime && mkdir -p runtime/$SYSROOT_PATH 105 | 106 | rm -rf tmp && mkdir tmp 107 | RUSTC_NAME="rustc-${{ env.active_toolchain }}" 108 | STD_NAME="rust-std-${{ env.active_toolchain }}" 109 | curl -L "http://static.rust-lang.org/dist/${RUSTC_NAME}.tar.gz" | tar xzvf - -C tmp/ 110 | curl -L "http://static.rust-lang.org/dist/${STD_NAME}.tar.gz" | tar xzvf - -C tmp/ 111 | 112 | ./tmp/${RUSTC_NAME}/install.sh --destdir=runtime/$SYSROOT_PATH --prefix= 113 | ./tmp/${STD_NAME}/install.sh --destdir=runtime/$SYSROOT_PATH --prefix= 114 | 115 | if [[ "${{ env.is_linux }}" == "true" ]]; then 116 | cp target/${{ env.host_tuple }}/${{ env.build_profile }}/rustowl${{ env.exec_ext }} ./runtime 117 | cp target/${{ env.host_tuple }}/${{ env.build_profile }}/rustowlc${{ env.exec_ext }} ./runtime 118 | else 119 | cp target/${{ env.build_profile }}/rustowl${{ env.exec_ext }} ./runtime 120 | cp target/${{ env.build_profile }}/rustowlc${{ env.exec_ext }} ./runtime 121 | fi 122 | 123 | cp README.md ./runtime 124 | cp LICENSE ./runtime 125 | 126 | find target -type d | grep -E 'rustowl-build-time-out$' | xargs -I % cp -r % ./ 127 | cp -r rustowl-build-time-out/completions ./runtime 128 | cp -r rustowl-build-time-out/man ./runtime 129 | 130 | rm -rf ${{ env.archive_name }} 131 | cd runtime 132 | 133 | if [[ "${{ env.is_windows }}" == "true" ]]; then 134 | powershell -c 'Compress-Archive -Path README.md, LICENSE, "rustowl${{ env.exec_ext }}", "rustowlc${{ env.exec_ext }}", "sysroot", "completions", "man" -DestinationPath "..\${{ env.archive_name }}" -CompressionLevel Optimal' 135 | else 136 | tar -czvf ../${{ env.archive_name }} README.md LICENSE rustowl${{ env.exec_ext }} rustowlc${{ env.exec_ext }} sysroot/ completions/ man/ 137 | fi 138 | 139 | cp ./rustowl${{ env.exec_ext }} ../rustowl-${{ env.host_tuple }}${{ env.exec_ext }} 140 | 141 | - name: Upload 142 | uses: actions/upload-artifact@v4 143 | with: 144 | name: rustowl-runtime-${{ env.host_tuple }} 145 | path: | 146 | rustowl-${{ env.host_tuple }}${{ env.exec_ext }} 147 | ${{ env.archive_name }} 148 | 149 | vscode: 150 | needs: 151 | - check 152 | runs-on: ubuntu-latest 153 | permissions: 154 | contents: write 155 | 156 | steps: 157 | - name: Checkout 158 | uses: actions/checkout@v4 159 | 160 | - name: Setup Node.js 161 | uses: actions/setup-node@v4 162 | with: 163 | node-version: 20 164 | 165 | - name: Install dependencies 166 | run: yarn install --frozen-locked 167 | working-directory: ./vscode 168 | 169 | - name: Create VSIX 170 | run: yarn build 171 | working-directory: ./vscode 172 | 173 | - name: Upload 174 | uses: actions/upload-artifact@v4 175 | with: 176 | name: rustowl-vscode 177 | path: vscode/**/*.vsix 178 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate Changelog 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | changelogen: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - run: | 18 | docker pull quay.io/git-chglog/git-chglog:latest 19 | docker run -v "$PWD":/workdir quay.io/git-chglog/git-chglog --tag-filter-pattern '^v\d+\.\d+\.\d+$' -o CHANGELOG.md 20 | 21 | - name: Create Pull Request 22 | uses: peter-evans/create-pull-request@v7 23 | with: 24 | add-paths: | 25 | CHANGELOG.md 26 | author: github-actions[bot] 27 | base: main 28 | branch: create-pull-request/autogenerate-changelog 29 | commit-message: "chore: update changelog" 30 | title: "Update Changelog" 31 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check RustOwl 2 | 3 | on: 4 | workflow_call: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | cargo: 11 | strategy: 12 | matrix: 13 | os: 14 | - ubuntu-latest 15 | - windows-latest 16 | - macos-latest 17 | - ubuntu-24.04-arm 18 | runs-on: ${{ matrix.os }} 19 | env: 20 | RUSTUP_TOOLCHAIN: 1.87.0 21 | RUSTC_BOOTSTRAP: 1 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Rust toolchain 27 | uses: dtolnay/rust-toolchain@stable 28 | with: 29 | toolchain: ${{ env.RUSTUP_TOOLCHAIN }} 30 | components: clippy,rustfmt,llvm-tools,rust-src,rustc-dev 31 | 32 | - run: cargo fmt --check 33 | - run: cargo clippy --all-targets --all-features -- -D warnings 34 | 35 | - name: Build 36 | run: cargo build --release 37 | 38 | - name: install binaries 39 | run: cargo install --path . 40 | 41 | - name: Run RustOwl 42 | run: rustowl check 43 | 44 | 45 | vscode: 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | - name: Setup Node.js 51 | uses: actions/setup-node@v4 52 | with: 53 | node-version: 20 54 | - run: yarn install --frozen-locked 55 | working-directory: ./vscode 56 | - run: yarn prettier -c src 57 | working-directory: ./vscode 58 | - run: yarn lint && yarn check-types 59 | working-directory: ./vscode 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release RustOwl 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | permissions: 9 | actions: read 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | uses: ./.github/workflows/build.yaml 15 | 16 | meta: 17 | runs-on: ubuntu-latest 18 | outputs: 19 | pre_release: ${{ steps.pre-release.outputs.pre_release }} 20 | steps: 21 | - name: Check pre-release 22 | id: pre-release 23 | run: | 24 | if [[ "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 25 | echo "pre_release=false" >> $GITHUB_OUTPUT 26 | else 27 | echo "pre_release=true" >> $GITHUB_OUTPUT 28 | fi 29 | 30 | crates-io-release: 31 | runs-on: ubuntu-latest 32 | needs: 33 | - build 34 | - meta 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Release crates.io 38 | if: needs.meta.outputs.pre_release != 'true' 39 | run: | 40 | echo '${{ secrets.CRATES_IO_API_TOKEN }}' | cargo login 41 | cargo publish 42 | 43 | vscode-release: 44 | runs-on: ubuntu-latest 45 | needs: 46 | - build 47 | - meta 48 | steps: 49 | - uses: actions/checkout@v4 50 | - name: Setup Node.js 51 | uses: actions/setup-node@v4 52 | with: 53 | node-version: 20 54 | - name: Release vsce 55 | if: needs.meta.outputs.pre_release != 'true' 56 | run: | 57 | yarn install --frozen-locked 58 | yarn vsce publish 59 | working-directory: ./vscode 60 | env: 61 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 62 | 63 | aur-release: 64 | runs-on: ubuntu-latest 65 | needs: 66 | - build 67 | - meta 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | - name: AUR Release 72 | uses: KSXGitHub/github-actions-deploy-aur@v4.1.1 73 | if: needs.meta.outputs.pre_release != 'true' 74 | with: 75 | pkgname: rustowl-bin 76 | pkgbuild: ./aur/PKGBUILD 77 | updpkgsums: true 78 | commit_username: ${{ secrets.AUR_USERNAME }} 79 | commit_email: ${{ secrets.AUR_EMAIL }} 80 | ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 81 | commit_message: Update AUR package 82 | ssh_keyscan_types: rsa,ecdsa,ed25519 83 | env: 84 | AUR_USERNAME: ${{ secrets.AUR_USERNAME }} 85 | AUR_EMAIL: ${{ secrets.AUR_EMAIL }} 86 | AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 87 | 88 | github-release: 89 | runs-on: ubuntu-latest 90 | needs: 91 | - build 92 | - meta 93 | steps: 94 | - uses: actions/checkout@v4 95 | with: 96 | fetch-depth: 0 97 | - name: Setup Node.js 98 | uses: actions/setup-node@v4 99 | with: 100 | node-version: 20 101 | - name: Generate Release Notes 102 | run: | 103 | npx changelogithub@latest --contributors --output release.md 104 | env: 105 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | - name: Download All Artifacts 107 | uses: actions/download-artifact@v4 108 | with: 109 | path: artifacts 110 | pattern: rustowl-* 111 | merge-multiple: true 112 | github-token: ${{ secrets.GITHUB_TOKEN }} 113 | run-ids: ${{ needs.build.outputs.run_id }} 114 | - name: Release 115 | uses: softprops/action-gh-release@v2 116 | with: 117 | files: artifacts/**/* 118 | draft: true 119 | body_path: release.md 120 | prerelease: ${{ needs.meta.outputs.pre_release == 'true' }} 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Nix stuff can be ignored, see https://github.com/cordx56/rustowl/issues/59 4 | result* 5 | .envrc 6 | .luarc.json 7 | 8 | # Ignore completions and man dir, they are generated at build time 9 | completions/ 10 | man/ 11 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = { 2 | "631", -- max_line_length 3 | "122", -- read-only field of global variable 4 | } 5 | read_globals = { 6 | "vim", 7 | } 8 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | line_endings = "Unix" 2 | indent_type = "Spaces" 3 | indent_width = 2 4 | quote_style = "AutoPreferSingle" 5 | call_parentheses = "NoSingleTable" 6 | collapse_simple_statement = "Never" 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Unreleased] 3 | 4 | ### 🎨 Chores 5 | - **aur:** bump rustup toolchain version ([#177](https://github.com/cordx56/rustowl/issues/177)) 6 | 7 | 8 | 9 | ## [v0.3.4] - 2025-05-20 10 | ### 🎨 Chores 11 | - urgent release v0.3.4, fixes wrong visualization 12 | - update changelog ([#170](https://github.com/cordx56/rustowl/issues/170)) 13 | - update changelog ([#168](https://github.com/cordx56/rustowl/issues/168)) 14 | 15 | ### 🐞 Bug Fixes 16 | - **lsp-core:** fix actual lifetime range visualization for `Drop` variable. 17 | 18 | 19 | 20 | ## [v0.3.3] - 2025-05-17 21 | ### ♻️ Code Refactoring 22 | - split build action from release action 23 | 24 | ### 🎨 Chores 25 | - fix pre-release if statement 26 | - fix release case 27 | - regex in bash should not quoted 28 | - automate cargo publish 29 | - vsce auto publish 30 | - use official toolchain 31 | - Rewrite CLI using Derive API ([#153](https://github.com/cordx56/rustowl/issues/153)) 32 | - update changelog ([#154](https://github.com/cordx56/rustowl/issues/154)) 33 | - **cli:** Add help messages to options ([#159](https://github.com/cordx56/rustowl/issues/159)) 34 | 35 | ### 🐞 Bug Fixes 36 | - support CRLF 37 | - GitHub Actions typo 38 | - use native ca certs by enabling native roots feature of reqwest ([#162](https://github.com/cordx56/rustowl/issues/162)) 39 | - **pkgbuild:** use rustup instead of cargo ([#156](https://github.com/cordx56/rustowl/issues/156)) 40 | 41 | ### 🚀 Features 42 | - update rustc to 1.87.0 43 | 44 | 45 | 46 | ## [v0.3.2] - 2025-05-09 47 | ### 🐞 Bug Fixes 48 | - support gsed (macOS) 49 | - version.sh removed and use ./scripts/bump.sh 50 | - specify pkg-fmt for binstall 51 | - restore current newest version 52 | 53 | ### 🚀 Features 54 | - v0.3.2 release 55 | - support RUSTOWL_SYSROOT_DIRS 56 | - add a bump.sh for bumping ([#148](https://github.com/cordx56/rustowl/issues/148)) 57 | - documented binstall method 58 | - support single .rs file analyze and VS Code download progress 59 | 60 | ### Pull Requests 61 | - Merge pull request [#146](https://github.com/cordx56/rustowl/issues/146) from cordx56/dependabot/npm_and_yarn/vscode/types/node-22.15.14 62 | 63 | 64 | 65 | ## [v0.3.1] - 2025-05-07 66 | ### 🎨 Chores 67 | - Release v0.3.1 68 | - Don't check every main push 69 | - update changelog 70 | - update changelog 71 | - update changelog 72 | - update changelog ([#116](https://github.com/cordx56/rustowl/issues/116)) 73 | - update changelog ([#112](https://github.com/cordx56/rustowl/issues/112)) 74 | - update changelog ([#104](https://github.com/cordx56/rustowl/issues/104)) 75 | - add comments to cargo.toml on next release changes 76 | - added build time env var description 77 | - update changelog 78 | - update changelog 79 | - update changelog 80 | 81 | ### 🐞 Bug Fixes 82 | - email 83 | - use target name in cp command 84 | - VS Code version check returns null 85 | - pr permission for changelog 86 | - dont use tar, use Compress-Archive instead 87 | - check before release and profile dir 88 | - add release on top of cp 89 | - change compress script to use sysroot dir ([#125](https://github.com/cordx56/rustowl/issues/125)) 90 | - arm Windows build 91 | - avoid failure to find sysroot 92 | - rustowlc ext for Windows 93 | - **aur:** add cd lines as it errors 94 | - **binstall:** use archives instead of binaries 95 | - **changelogen:** only add normal releases, not alpha and others 96 | - **ci:** use powershell in windoes ci 97 | - **reqwest:** dont depend on openssl-sys, use rustls for lower system deps 98 | - **windows:** unzip 99 | 100 | ### 🚀 Features 101 | - better-release-notes 102 | - support multiple fallbacks 103 | - remove redundant rustc_driver 104 | - RustOwl version check for VS Code extension 105 | - add a pr template 106 | - add a code of conduct and security file 107 | - aur packages ([#105](https://github.com/cordx56/rustowl/issues/105)) 108 | - aur packages 109 | - automatic updates with dependabot 110 | - use zip instead of tar in windows 111 | - auto release changelogs, changelog generation 112 | - **archive:** implement zipping for windows 113 | 114 | ### Reverts 115 | - move CONTRIBUTING.md 116 | 117 | ### Pull Requests 118 | - Merge pull request [#142](https://github.com/cordx56/rustowl/issues/142) from cordx56/feat/better-release-notes 119 | - Merge pull request [#140](https://github.com/cordx56/rustowl/issues/140) from MuntasirSZN/fix/changelogen 120 | - Merge pull request [#132](https://github.com/cordx56/rustowl/issues/132) from cordx56/create-pull-request/autogenerate-changelog 121 | - Merge pull request [#131](https://github.com/cordx56/rustowl/issues/131) from MuntasirSZN/fix/windows-unzip 122 | - Merge pull request [#130](https://github.com/cordx56/rustowl/issues/130) from MuntasirSZN/fix/pkgbuild-git 123 | - Merge pull request [#129](https://github.com/cordx56/rustowl/issues/129) from MuntasirSZN/feat/community-standards 124 | - Merge pull request [#128](https://github.com/cordx56/rustowl/issues/128) from MuntasirSZN/main 125 | - Merge pull request [#126](https://github.com/cordx56/rustowl/issues/126) from cordx56/create-pull-request/autogenerate-changelog 126 | - Merge pull request [#124](https://github.com/cordx56/rustowl/issues/124) from MuntasirSZN/main 127 | - Merge pull request [#123](https://github.com/cordx56/rustowl/issues/123) from MuntasirSZN/main 128 | - Merge pull request [#115](https://github.com/cordx56/rustowl/issues/115) from MuntasirSZN/main 129 | - Merge pull request [#114](https://github.com/cordx56/rustowl/issues/114) from MuntasirSZN/main 130 | - Merge pull request [#113](https://github.com/cordx56/rustowl/issues/113) from MuntasirSZN/main 131 | - Merge pull request [#111](https://github.com/cordx56/rustowl/issues/111) from MuntasirSZN/fix/archive-ci 132 | - Merge pull request [#103](https://github.com/cordx56/rustowl/issues/103) from MuntasirSZN/feat/dependabot 133 | - Merge pull request [#101](https://github.com/cordx56/rustowl/issues/101) from MuntasirSZN/feat/zig-linker 134 | - Merge pull request [#96](https://github.com/cordx56/rustowl/issues/96) from MuntasirSZN/main 135 | - Merge pull request [#97](https://github.com/cordx56/rustowl/issues/97) from MuntasirSZN/fix/binstall 136 | - Merge pull request [#99](https://github.com/cordx56/rustowl/issues/99) from Alex-Grimes/enhancment/78_Add-highlight-style-config-option 137 | - Merge pull request [#98](https://github.com/cordx56/rustowl/issues/98) from cordx56/fix/ci-changelogen 138 | - Merge pull request [#92](https://github.com/cordx56/rustowl/issues/92) from MuntasirSZN/main 139 | - Merge pull request [#94](https://github.com/cordx56/rustowl/issues/94) from mrcjkb/mj/push-mpkursvmrosw 140 | - Merge pull request [#91](https://github.com/cordx56/rustowl/issues/91) from MuntasirSZN/main 141 | 142 | 143 | 144 | ## [v0.3.0] - 2025-04-30 145 | ### 🚀 Features 146 | - shell completions and man pages 147 | 148 | ### Reverts 149 | - test workflow 150 | 151 | ### Pull Requests 152 | - Merge pull request [#88](https://github.com/cordx56/rustowl/issues/88) from yasuo-ozu/fix_build_canonical 153 | - Merge pull request [#85](https://github.com/cordx56/rustowl/issues/85) from MuntasirSZN/main 154 | - Merge pull request [#80](https://github.com/cordx56/rustowl/issues/80) from siketyan/ci/more-platform 155 | 156 | 157 | 158 | ## [v0.2.2] - 2025-04-18 159 | ### ♻️ Code Refactoring 160 | - streamline toolchain detection and correct cargo path 161 | 162 | ### 🚀 Features 163 | - **toolchain:** add support for RUSTOWL_TOOLCHAIN_DIR to bypass rustup 164 | 165 | ### Pull Requests 166 | - Merge pull request [#77](https://github.com/cordx56/rustowl/issues/77) from xBLACKICEx/flexible-toolchain 167 | 168 | 169 | 170 | ## [v0.2.1] - 2025-04-15 171 | 172 | 173 | ## [v0.2.0] - 2025-04-09 174 | ### ♻️ Code Refactoring 175 | - add prefix to functions with commonly used names 176 | 177 | ### 🎨 Chores 178 | - add require lsp 179 | - remove calling `enable-rustowlsp-cursor` 180 | - add `defgroup` 181 | - add `provide` 182 | - Migrate to Rust 2024 183 | 184 | ### 🐞 Bug Fixes 185 | - package-requires 186 | 187 | ### Reverts 188 | - messsage type 189 | - neovim plugin function 190 | - update install manual 191 | 192 | ### Pull Requests 193 | - Merge pull request [#72](https://github.com/cordx56/rustowl/issues/72) from mawkler/neovim-version 194 | - Merge pull request [#69](https://github.com/cordx56/rustowl/issues/69) from cordx56/feat/elim-rustup-call 195 | - Merge pull request [#48](https://github.com/cordx56/rustowl/issues/48) from mawkler/lua-api 196 | - Merge pull request [#62](https://github.com/cordx56/rustowl/issues/62) from Kyure-A/main 197 | - Merge pull request [#61](https://github.com/cordx56/rustowl/issues/61) from AIDIGIT/nvim-hl-priorities 198 | - Merge pull request [#60](https://github.com/cordx56/rustowl/issues/60) from AIDIGIT/main 199 | - Merge pull request [#55](https://github.com/cordx56/rustowl/issues/55) from sorairolake/migrate-to-2024-edition 200 | 201 | 202 | 203 | ## [v0.1.4] - 2025-02-22 204 | ### ♻️ Code Refactoring 205 | - simplify HashMap insertion by using entry API 206 | 207 | ### Pull Requests 208 | - Merge pull request [#54](https://github.com/cordx56/rustowl/issues/54) from uhobnil/main 209 | 210 | 211 | 212 | ## [v0.1.3] - 2025-02-20 213 | ### 🎨 Chores 214 | - remove duplicate code 215 | 216 | ### 🐞 Bug Fixes 217 | - install the newest version 218 | 219 | ### Pull Requests 220 | - Merge pull request [#53](https://github.com/cordx56/rustowl/issues/53) from uhobnil/main 221 | - Merge pull request [#47](https://github.com/cordx56/rustowl/issues/47) from robin-thoene/fix/update-install-script 222 | 223 | 224 | 225 | ## [v0.1.2] - 2025-02-19 226 | ### 🎨 Chores 227 | - add the description for duplication 228 | - add config.yaml 229 | - add issue templae for feature requesting 230 | - add labels to bug_report 231 | - add issue templae for bug reporing 232 | 233 | ### 🐞 Bug Fixes 234 | - s/enhancement/bug/ 235 | - update the introduction 236 | - correct label 237 | - remove redundant textarea 238 | - update the information 239 | - update the file extension 240 | - s/rustowl/RustOwl/ 241 | - kill process when the client/server is dead 242 | 243 | ### Pull Requests 244 | - Merge pull request [#35](https://github.com/cordx56/rustowl/issues/35) from chansuke/chore/add-issue-template 245 | - Merge pull request [#42](https://github.com/cordx56/rustowl/issues/42) from uhobnil/main 246 | - Merge pull request [#34](https://github.com/cordx56/rustowl/issues/34) from mtshiba/main 247 | - Merge pull request [#26](https://github.com/cordx56/rustowl/issues/26) from Toyo-tez/main 248 | - Merge pull request [#11](https://github.com/cordx56/rustowl/issues/11) from wx257osn2/clippy 249 | - Merge pull request [#24](https://github.com/cordx56/rustowl/issues/24) from mawkler/main 250 | 251 | 252 | 253 | ## [v0.1.1] - 2025-02-07 254 | 255 | 256 | ## [v0.1.0] - 2025-02-05 257 | ### Pull Requests 258 | - Merge pull request [#2](https://github.com/cordx56/rustowl/issues/2) from wx257osn2/support-windows 259 | 260 | 261 | 262 | ## [v0.0.5] - 2025-02-02 263 | 264 | 265 | ## [v0.0.4] - 2025-01-31 266 | 267 | 268 | ## [v0.0.3] - 2025-01-30 269 | ### Pull Requests 270 | - Merge pull request [#6](https://github.com/cordx56/rustowl/issues/6) from Jayllyz/build/enable-lto-codegen 271 | - Merge pull request [#5](https://github.com/cordx56/rustowl/issues/5) from mu001999-contrib/main 272 | 273 | 274 | 275 | ## [v0.0.2] - 2025-01-23 276 | 277 | 278 | ## v0.0.1 - 2024-11-13 279 | 280 | [Unreleased]: https://github.com/cordx56/rustowl/compare/v0.3.4...HEAD 281 | [v0.3.4]: https://github.com/cordx56/rustowl/compare/v0.3.3...v0.3.4 282 | [v0.3.3]: https://github.com/cordx56/rustowl/compare/v0.3.2...v0.3.3 283 | [v0.3.2]: https://github.com/cordx56/rustowl/compare/v0.3.1...v0.3.2 284 | [v0.3.1]: https://github.com/cordx56/rustowl/compare/v0.3.0...v0.3.1 285 | [v0.3.0]: https://github.com/cordx56/rustowl/compare/v0.2.2...v0.3.0 286 | [v0.2.2]: https://github.com/cordx56/rustowl/compare/v0.2.1...v0.2.2 287 | [v0.2.1]: https://github.com/cordx56/rustowl/compare/v0.2.0...v0.2.1 288 | [v0.2.0]: https://github.com/cordx56/rustowl/compare/v0.1.4...v0.2.0 289 | [v0.1.4]: https://github.com/cordx56/rustowl/compare/v0.1.3...v0.1.4 290 | [v0.1.3]: https://github.com/cordx56/rustowl/compare/v0.1.2...v0.1.3 291 | [v0.1.2]: https://github.com/cordx56/rustowl/compare/v0.1.1...v0.1.2 292 | [v0.1.1]: https://github.com/cordx56/rustowl/compare/v0.1.0...v0.1.1 293 | [v0.1.0]: https://github.com/cordx56/rustowl/compare/v0.0.5...v0.1.0 294 | [v0.0.5]: https://github.com/cordx56/rustowl/compare/v0.0.4...v0.0.5 295 | [v0.0.4]: https://github.com/cordx56/rustowl/compare/v0.0.3...v0.0.4 296 | [v0.0.3]: https://github.com/cordx56/rustowl/compare/v0.0.2...v0.0.3 297 | [v0.0.2]: https://github.com/cordx56/rustowl/compare/v0.0.1...v0.0.2 298 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustowl" 3 | version = "0.3.4" 4 | edition = "2024" 5 | authors = ["cordx56 "] 6 | description = "Visualize Ownership and Lifetimes in Rust" 7 | documentation = "https://github.com/cordx56/rustowl/blob/main/README.md" 8 | readme = "README.md" 9 | repository = "https://github.com/cordx56/rustowl" 10 | license = "MPL-2.0" 11 | keywords = ["visualization", "ownership", "lifetime", "lsp"] 12 | categories = ["development-tools", "visualization"] 13 | 14 | [dependencies] 15 | serde = { version = "1.0.210", features = ["derive"] } 16 | serde_json = "1.0.135" 17 | log = "0.4.22" 18 | simple_logger = { version = "5.0.0", features = ["stderr"] } 19 | tokio = { version = "1.45.1", features = ["rt", "rt-multi-thread", "macros", "sync", "time", "io-std", "io-util", "process", "fs"] } 20 | tower-lsp = "0.20.0" 21 | process_alive = "0.1.1" 22 | cargo_metadata = "0.20.0" 23 | uuid = { version = "1", features = ["v4"] } 24 | clap = { version = "4.5.39", features = ["cargo", "derive"] } 25 | tar = "0.4.44" 26 | flate2 = "1.1.1" 27 | reqwest = { version = "0.12.18", default-features = false, features = ["http2", "rustls-tls-native-roots"] } 28 | clap_complete_nushell = "4.5.6" 29 | clap_complete = "4.5.52" 30 | zip = "4.0.0" 31 | 32 | [build-dependencies] 33 | clap_complete_nushell = "4.5.6" 34 | clap_complete = "4.5.52" 35 | clap_mangen = "0.2.26" 36 | clap = { version = "4.5.39", features = ["derive"] } 37 | 38 | [target.'cfg(unix)'.dependencies] 39 | libc = "0.2.169" 40 | 41 | # This is cited from [rustc](https://github.com/rust-lang/rust/blob/1.86.0/compiler/rustc/Cargo.toml). 42 | # MIT License 43 | [target.'cfg(unix)'.dependencies.tikv-jemalloc-sys] 44 | version = "0.6.0" 45 | optional = true 46 | features = ['unprefixed_malloc_on_supported_platforms'] 47 | 48 | [features] 49 | default = ["jemalloc"] 50 | jemalloc = ['dep:tikv-jemalloc-sys'] 51 | 52 | [profile.release] 53 | opt-level = 3 54 | lto = "fat" 55 | codegen-units = 1 56 | 57 | [profile.arm-windows-release] 58 | inherits = "release" 59 | lto = "off" 60 | 61 | [package.metadata.rust-analyzer] 62 | rustc_private = true 63 | 64 | [package.metadata.binstall] 65 | pkg-url = "{ repo }/releases/download/v{ version }/rustowl-{ target }{ archive-suffix }" 66 | pkg-fmt = "tgz" 67 | disabled-strategies = ["quick-install", "compile"] 68 | 69 | [package.metadata.binstall.overrides.x86_64-pc-windows-msvc] 70 | pkg-fmt = "zip" 71 | 72 | [package.metadata.binstall.overrides.aarch64-pc-windows-msvc] 73 | pkg-fmt = "zip" 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 5 | RustOwl 6 | 7 |

8 |

9 | Visualize ownership and lifetimes in Rust for debugging and optimization 10 |

11 |

12 | 13 |

14 |
15 | 16 | RustOwl visualizes ownership movement and lifetimes of variables. 17 | When you save Rust source code, it is analyzed, and the ownership and lifetimes of variables are visualized when you hover over a variable or function call. 18 | 19 | RustOwl visualizes those by using underlines: 20 | 21 | - 🟩 green: variable's actual lifetime 22 | - 🟦 blue: immutable borrowing 23 | - 🟪 purple: mutable borrowing 24 | - 🟧 orange: value moved / function call 25 | - 🟥 red: lifetime error 26 | - diff of lifetime between actual and expected, or 27 | - invalid overlapped lifetime of mutable and shared (immutable) references 28 | 29 | Detailed usage is described [here](docs/usage.md). 30 | 31 | Currently, we offer VSCode extension, Neovim plugin and Emacs package. 32 | For these editors, move the text cursor over the variable or function call you want to inspect and wait for 2 seconds to visualize the information. 33 | We implemented LSP server with an extended protocol. 34 | So, RustOwl can be used easily from other editor. 35 | 36 | ## Support 37 | 38 | If you're looking for support, please consider checking all issues, existing discussions, and [starting a discussion](https://github.com/cordx56/rustowl/discussions/new?category=q-a) first! 39 | 40 | ## Quick Start 41 | 42 | Here we describe how to start using RustOwl with VS Code. 43 | 44 | ### Prerequisite 45 | 46 | - `cargo` installed 47 | - You can install `cargo` using `rustup` from [this link](https://rustup.rs/). 48 | - Visual Studio Code (VS Code) installed 49 | 50 | We tested this guide on macOS Sequoia 15.3.2 on arm64 architecture with VS Code 1.99.3 and `cargo` 1.87.0. 51 | 52 | ### VS Code 53 | 54 | You can install VS Code extension from [this link](https://marketplace.visualstudio.com/items?itemName=cordx56.rustowl-vscode). 55 | RustOwl will be installed automatically when the extension is activated. 56 | 57 | After installation, the extension will automatically run RustOwl when you save any Rust program in cargo workspace. 58 | The initial analysis may take some time, but from the second run onward, compile caching is used to reduce the analysis time. 59 | 60 | ## Other editor support 61 | 62 | We support Neovim and Emacs. 63 | You have to [install RustOwl](docs/installation.md) before using RustOwl with other editors. 64 | 65 | You can also create your own LSP client. 66 | If you would like to implement a client, please refer to the [The RustOwl LSP specification](docs/lsp-spec.md). 67 | 68 | ### Neovim 69 | 70 | Minimal setup with [lazy.nvim](https://github.com/folke/lazy.nvim): 71 | 72 | ```lua 73 | { 74 | 'cordx56/rustowl', 75 | version = '*', -- Latest stable version 76 | build = 'cargo binstall rustowl', 77 | lazy = false, -- This plugin is already lazy 78 | opts = {}, 79 | } 80 | ``` 81 | 82 |
83 | Recommended configuration: Click to expand 84 | 85 | ```lua 86 | { 87 | 'cordx56/rustowl', 88 | version = '*', -- Latest stable version 89 | build = 'cargo binstall rustowl', 90 | lazy = false, -- This plugin is already lazy 91 | opts = { 92 | client = { 93 | on_attach = function(_, buffer) 94 | vim.keymap.set('n', 'o', function() 95 | require('rustowl').toggle(buffer) 96 | end, { buffer = buffer, desc = 'Toggle RustOwl' }) 97 | end 98 | }, 99 | }, 100 | } 101 | ``` 102 | 103 |
104 | 105 | Default options: 106 | 107 | ```lua 108 | { 109 | auto_attach = true, -- Auto attach the RustOwl LSP client when opening a Rust file 110 | auto_enable = false, -- Enable RustOwl immediately when attaching the LSP client 111 | idle_time = 500, -- Time in milliseconds to hover with the cursor before triggering RustOwl 112 | client = {}, -- LSP client configuration that gets passed to `vim.lsp.start` 113 | highlight_style = 'undercurl', -- You can also use 'underline' 114 | } 115 | ``` 116 | 117 | When opening a Rust file, the Neovim plugin creates the `Rustowl` user command: 118 | 119 | ```vim 120 | :Rustowl {subcommand} 121 | ``` 122 | 123 | where `{subcommand}` can be one of: 124 | 125 | - `start_client`: Start the rustowl LSP client. 126 | - `stop_client`: Stop the rustowl LSP client. 127 | - `restart_client`: Restart the rustowl LSP client. 128 | - `enable`: Enable rustowl highlights. 129 | - `disable`: Disable rustowl highlights. 130 | - `toggle`: Toggle rustowl highlights. 131 | 132 | 133 | ### Emacs 134 | 135 | Elpaca example: 136 | 137 | ```elisp 138 | (elpaca 139 | (rustowlsp 140 | :host github 141 | :repo "cordx56/rustowl")) 142 | ``` 143 | 144 | You have to install RustOwl LSP server manually. 145 | 146 | ### RustRover / IntelliJ IDEs 147 | 148 | There is a [third-party repository](https://github.com/siketyan/intellij-rustowl) that supports IntelliJ IDEs. 149 | You have to install RustOwl LSP server manually. 150 | 151 | ## Architecture / OS / package repositories 152 | 153 | ### Archlinux 154 | 155 | We have an AUR package. Run: 156 | 157 | ```sh 158 | yay -S rustowl-bin 159 | ``` 160 | 161 | Replace `yay` with your AUR helper of choice. 162 | 163 | We also have a git version, that builds from source: 164 | 165 | ```sh 166 | yay -S rustowl-git 167 | ``` 168 | 169 | ### Nix flake 170 | 171 | There is a [third-party Nix flake repository](https://github.com/nix-community/rustowl-flake) in the Nix community. 172 | 173 | ## Build manually 174 | 175 | Here, we describe manual install instructions from source code. 176 | 177 | ### RustOwl 178 | 179 | #### Prerequisite 180 | 181 | - `rustup` installed 182 | - You can install `rustup` from [this link](https://rustup.rs/). 183 | - You need to set up the `PATH` environment variable. To do this, follow the instructions provided by the `rustup` installer. 184 | 185 | Building RustOwl requires nightly build of `rustc`. It will automatically installed by `rustup`. 186 | 187 | RustOwl has been tested on macOS Sequoia 15.3.2 on arm64 architecture with `rustup` 1.28.1. 188 | We have not tested the installation of dependencies from other package repositories, such as Homebrew. 189 | You may need to uninstall any Rust-related packages installed through those repositories first. 190 | Other dependencies are locked in the configuration files and will be installed automatically. 191 | 192 | We have also tested this on Ubuntu 24.04.2 on amd64 architecture and on Windows 11 Education 23H2 on amd64 architecture. 193 | Additional dependencies may be required. 194 | We have confirmed that running `apt install build-essential` is necessary on a freshly installed Ubuntu for linking. 195 | 196 | #### Build & Run 197 | 198 | ```bash 199 | cargo install --path . --locked 200 | ``` 201 | 202 | You can add runtime directory paths to the search paths by specifying `RUSTOWL_RUNTIME_DIRS` or `RUSTOWL_SYSROOTS`. 203 | 204 | ### VSCode extension 205 | 206 | #### Prerequisite 207 | 208 | - VS Code installed 209 | - You can install VS Code from [this link](https://code.visualstudio.com/). 210 | - Node.js installed 211 | - `yarn` installed 212 | - After installing Node.js, You can install `yarn` by running `npm install -g yarn`. 213 | 214 | VS Code extension has been tested on macOS Sequoia 15.3.2 on arm64 architecture with Visual Studio Code 1.99.3, Node.js v20.16.0, and `yarn` 1.22.22. 215 | Other dependencies are locked in the configuration files and will be installed automatically. 216 | 217 | #### Build & Run 218 | 219 | First, install the dependencies. 220 | 221 | ```bash 222 | cd vscode 223 | yarn install --frozen-lockfile 224 | ``` 225 | 226 | Then open `vscode` directory in VS Code. 227 | 228 | A notification to install the recommended VS Code extension will appear in the bottom right corner of VS Code. 229 | Click the install button, wait for the installation to finish, and then restart VS Code. 230 | 231 | Open `vscode` directory again, and press the `F5` key in the VS Code window. 232 | A new VS Code window with the extension enabled will appear. 233 | 234 | Open cargo workspace directory in the new VS Code window. 235 | 236 | When you save Rust files, decoration indicating the movement of ownership and lifetimes will appear in the editor. 237 | 238 | 239 | ## Note 240 | 241 | In this tool, due to the limitations of VS Code's decoration specifications, characters with descenders, such as g or parentheses, may occasionally not display underlines properly. 242 | Additionally, we observed that the `println!` macro sometimes produces extra output, though this does not affect usability in any significant way. 243 | -------------------------------------------------------------------------------- /aur/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: MuntasirSZN 2 | # Maintainer: cordx56 3 | 4 | pkgname=rustowl-bin 5 | pkgver=0.3.4 6 | pkgrel=1 7 | pkgdesc='Visualize Ownership and Lifetimes in Rust' 8 | url='https://github.com/cordx56/rustowl' 9 | license=('MPL-2.0') 10 | makedepends=('rustup' 'zig=0.13.0') 11 | depends=() 12 | conflicts=('rustowl-git') 13 | arch=('any') 14 | source=("https://github.com/cordx56/rustowl/archive/refs/tags/v${pkgver}.tar.gz") 15 | sha256sums=('fa120643aeb48061eb32a7c993dabff88aa4e9d0b32f8ab0f3289b3fb2cf5744') 16 | 17 | prepare() { 18 | cd rustowl-${pkgver} 19 | export RUSTC_BOOTSTRAP=1 20 | export RUSTUP_TOOLCHAIN=1.87.0 21 | rustup component add rust-src rustc-dev llvm-tools 22 | cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" 23 | cargo install --locked cargo-zigbuild 24 | } 25 | 26 | build() { 27 | cd rustowl-${pkgver} 28 | export CARGO_TARGET_DIR=target 29 | export RUSTC_BOOTSTRAP=1 30 | export RUSTUP_TOOLCHAIN=1.87.0 31 | export RUSTOWL_RUNTIME_DIRS=/opt/rustowl 32 | cargo zigbuild --frozen --release --all-features --target $(rustc --print=host-tuple).2.17 33 | } 34 | 35 | check() { 36 | cd rustowl-${pkgver} 37 | export RUSTC_BOOTSTRAP=1 38 | export RUSTUP_TOOLCHAIN=1.87.0 39 | cargo test --frozen --all-features 40 | } 41 | 42 | package() { 43 | cd rustowl-${pkgver} 44 | find target -type d | grep -E 'rustowl-build-time-out$' | xargs -I % cp -r % ./ 45 | mkdir sysroot 46 | ACTIVE_TOOLCHAIN="$(rustup show active-toolchain | awk '{ print $1 }')" 47 | cp -r "$(rustc --print=sysroot)" sysroot/$ACTIVE_TOOLCHAIN 48 | find sysroot -type f | grep -v -E '\.(rlib|so|dylib|dll)$' | xargs rm -rf 49 | find sysroot -depth -type d -empty -exec rm -rf {} \; 50 | install -d -m 755 "$pkgdir/opt/rustowl" 51 | cp -a sysroot/ "$pkgdir/opt/rustowl/" 52 | install -Dm0755 -t "$pkgdir/usr/bin/" "target/$(rustc --print=host-tuple)/release/rustowl" 53 | install -Dm0755 -t "$pkgdir/usr/bin/" "target/$(rustc --print=host-tuple)/release/rustowlc" 54 | install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/rustowl/LICENSE" 55 | install -Dm644 rustowl-build-time-out/man/rustowl.1 "$pkgdir/usr/share/man/man1/rustowl.1" 56 | install -Dm644 "rustowl-build-time-out/completions/rustowl.bash" "${pkgdir}/usr/share/bash-completion/completions/rustowl" 57 | install -Dm644 "rustowl-build-time-out/completions/_rustowl" "${pkgdir}/usr/share/zsh/site-functions/_rustowl" 58 | install -Dm644 "rustowl-build-time-out/completions/rustowl.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/rustowl.fish" 59 | install -Dm644 "rustowl-build-time-out/completions/rustowl.elv" "${pkgdir}/usr/share/elvish/completions/rustowl.elv" 60 | install -Dm644 "rustowl-build-time-out/completions/_rustowl.ps1" "${pkgdir}/usr/share/powershell/Modules/Rustowl/_rustowl.ps1" 61 | } 62 | 63 | -------------------------------------------------------------------------------- /aur/PKGBUILD-GIT: -------------------------------------------------------------------------------- 1 | # Maintainer: MuntasirSZN 2 | # Maintainer: cordx56 3 | 4 | pkgname=rustowl-git 5 | pkgver=r1.0 6 | pkgrel=1 7 | pkgdesc='Visualize Ownership and Lifetimes in Rust' 8 | url='https://github.com/cordx56/rustowl' 9 | license=('MPL-2.0') 10 | makedepends=('git' 'rustup' 'zig=0.13.0') 11 | arch=('any') 12 | source=("git+https://github.com/cordx56/rustowl.git") 13 | sha256sums=('SKIP') 14 | conflicts=('rustowl-bin') 15 | 16 | pkgver() { 17 | cd "$srcdir/rustowl" 18 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 19 | } 20 | 21 | prepare() { 22 | cd "$srcdir/rustowl" 23 | export RUSTC_BOOTSTRAP=1 24 | export RUSTUP_TOOLCHAIN=1.87.0 25 | rustup component add rust-src rustc-dev llvm-tools 26 | cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" 27 | cargo install --locked cargo-zigbuild 28 | } 29 | 30 | build() { 31 | cd "$srcdir/rustowl" 32 | export CARGO_TARGET_DIR=target 33 | export RUSTC_BOOTSTRAP=1 34 | export RUSTUP_TOOLCHAIN=1.87.0 35 | export RUSTOWL_RUNTIME_DIRS=/opt/rustowl 36 | cargo zigbuild --frozen --release --all-features --target $(rustc --print=host-tuple).2.17 37 | } 38 | 39 | check() { 40 | cd "$srcdir/rustowl" 41 | export RUSTC_BOOTSTRAP=1 42 | export RUSTUP_TOOLCHAIN=1.87.0 43 | cargo test --frozen --all-features 44 | } 45 | 46 | package() { 47 | cd "$srcdir/rustowl" 48 | find target -type d | grep -E 'rustowl-build-time-out$' | xargs -I % cp -r % ./ 49 | mkdir sysroot 50 | ACTIVE_TOOLCHAIN="$(rustup show active-toolchain | awk '{ print $1 }')" 51 | cp -r "$(rustc --print=sysroot)" sysroot/$ACTIVE_TOOLCHAIN 52 | find sysroot -type f | grep -v -E '\.(rlib|so|dylib|dll)$' | xargs rm -rf 53 | find sysroot -depth -type d -empty -exec rm -rf {} \; 54 | install -d -m 755 "$pkgdir/opt/rustowl" 55 | cp -a sysroot/ "$pkgdir/opt/rustowl/" 56 | install -Dm0755 -t "$pkgdir/usr/bin/" "target/$(rustc --print=host-tuple)/release/rustowl" 57 | install -Dm0755 -t "$pkgdir/usr/bin/" "target/$(rustc --print=host-tuple)/release/rustowlc" 58 | install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/rustowl/LICENSE" 59 | install -Dm644 rustowl-build-time-out/man/rustowl.1 "$pkgdir/usr/share/man/man1/rustowl.1" 60 | install -Dm644 "rustowl-build-time-out/completions/rustowl.bash" "${pkgdir}/usr/share/bash-completion/completions/rustowl" 61 | install -Dm644 "rustowl-build-time-out/completions/_rustowl" "${pkgdir}/usr/share/zsh/site-functions/_rustowl" 62 | install -Dm644 "rustowl-build-time-out/completions/rustowl.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/rustowl.fish" 63 | install -Dm644 "rustowl-build-time-out/completions/rustowl.elv" "${pkgdir}/usr/share/elvish/completions/rustowl.elv" 64 | install -Dm644 "rustowl-build-time-out/completions/_rustowl.ps1" "${pkgdir}/usr/share/powershell/Modules/Rustowl/_rustowl.ps1" 65 | } 66 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use clap::CommandFactory; 2 | use clap_complete::generate_to; 3 | use std::env; 4 | use std::fs; 5 | use std::io::Error; 6 | use std::process::Command; 7 | 8 | include!("src/cli.rs"); 9 | include!("src/shells.rs"); 10 | 11 | fn main() -> Result<(), Error> { 12 | println!("cargo::rustc-env=RUSTOWL_TOOLCHAIN={}", get_toolchain()); 13 | 14 | #[cfg(not(windows))] 15 | let tarball_name = format!("rustowl-{}.tar.gz", get_host_tuple().unwrap()); 16 | 17 | #[cfg(windows)] 18 | let tarball_name = format!("rustowl-{}.zip", get_host_tuple().unwrap()); 19 | 20 | println!("cargo::rustc-env=RUSTOWL_ARCHIVE_NAME={tarball_name}"); 21 | 22 | let sysroot = get_sysroot().unwrap(); 23 | set_rustc_driver_path(&sysroot); 24 | 25 | let out_dir = Path::new(&env::var("OUT_DIR").expect("OUT_DIR unset. Expected path.")) 26 | .join("rustowl-build-time-out"); 27 | let mut cmd = Cli::command(); 28 | let completion_out_dir = out_dir.join("completions"); 29 | fs::create_dir_all(&completion_out_dir)?; 30 | 31 | for shell in Shell::value_variants() { 32 | generate_to(*shell, &mut cmd, "rustowl", &completion_out_dir)?; 33 | } 34 | let man_out_dir = out_dir.join("man"); 35 | fs::create_dir_all(&man_out_dir)?; 36 | let man = clap_mangen::Man::new(cmd); 37 | let mut buffer: Vec = Default::default(); 38 | man.render(&mut buffer)?; 39 | 40 | std::fs::write(man_out_dir.join("rustowl.1"), buffer)?; 41 | 42 | Ok(()) 43 | } 44 | 45 | // get toolchain 46 | fn get_toolchain() -> String { 47 | env::var("RUSTUP_TOOLCHAIN").expect("RUSTUP_TOOLCHAIN unset. Expected version.") 48 | } 49 | fn get_host_tuple() -> Option { 50 | match Command::new(env::var("RUSTC").unwrap_or("rustc".to_string())) 51 | .arg("--print=host-tuple") 52 | .output() 53 | { 54 | Ok(v) => Some(String::from_utf8(v.stdout).unwrap().trim().to_string()), 55 | Err(_) => None, 56 | } 57 | } 58 | // output rustc_driver path 59 | fn get_sysroot() -> Option { 60 | match Command::new(env::var("RUSTC").expect("RUSTC unset. Expected rustc path.")) 61 | .arg("--print=sysroot") 62 | .output() 63 | { 64 | Ok(v) => Some(String::from_utf8(v.stdout).unwrap().trim().to_string()), 65 | Err(_) => None, 66 | } 67 | } 68 | use std::fs::read_dir; 69 | use std::path::PathBuf; 70 | fn recursive_read_dir(path: impl AsRef) -> Vec { 71 | let mut paths = Vec::new(); 72 | for entry in read_dir(path).unwrap() { 73 | let entry = entry.unwrap(); 74 | let path = entry.path(); 75 | if path.is_dir() { 76 | paths.extend_from_slice(&recursive_read_dir(&path)); 77 | } else { 78 | paths.push(path); 79 | } 80 | } 81 | paths 82 | } 83 | fn set_rustc_driver_path(sysroot: &str) { 84 | for file in recursive_read_dir(sysroot) { 85 | if let Some(ext) = file.extension().and_then(|e| e.to_str()) { 86 | if matches!(ext, "rlib" | "so" | "dylib" | "dll") { 87 | let rel_path = file.strip_prefix(sysroot).unwrap(); 88 | let file_name = rel_path.file_name().unwrap().to_string_lossy(); 89 | if file_name.contains("rustc_driver-") { 90 | println!("cargo::rustc-env=RUSTC_DRIVER_NAME={file_name}"); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [cordx56@cordx.cx](mailto:cordx56@cordx.cx). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [faq]: https://www.contributor-covenant.org/faq 129 | [homepage]: https://www.contributor-covenant.org 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [translations]: https://www.contributor-covenant.org/translations 132 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 133 | 134 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guide 2 | 3 | _This document is under construction_. 4 | 5 | Thank you for considering to contribute RustOwl! 6 | 7 | In this document we describe how to contribute our project, as follows: 8 | 9 | - How to setup development environment 10 | - Checklist before submitting PR 11 | 12 | ## Set up your environment 13 | 14 | Here we describe how to set up your development environment. 15 | 16 | ### Rust code 17 | 18 | In the Rust code, we utilize nightly compiler features, which require some tweaks. 19 | Before starting this section, you might be required to install `rustup` since our project requires nightly compiler. 20 | 21 | #### Build and test using the nightly environment 22 | 23 | For building, testing, or installing, you can do the same as any common Rust project using the `cargo` command. 24 | Here, `cargo` must be a `rustup`-proxied command, which is usually installed with `rustup`. 25 | 26 | #### Build with stable Rust compiler 27 | 28 | To distribute release binary, we use stable Rust compiler to ship RustOwl with stable Rust compiler for users. 29 | 30 | The executable binary named `rustowlc`, which is one of the components of RustOwl, behaves like a Rust compiler. 31 | So we would like to compile `rustowlc`, which uses nightly features, with the stable Rust compiler. 32 | 33 | Note: Using this method is strongly discouraged officially. See [Unstable Book](doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html). 34 | 35 | To compile `rustowlc` with stable compiler, you should set environment variable as `RUSTC_BOOTSTRAP=1`. 36 | 37 | For example building with stable 1.87.0 Rust compiler: 38 | 39 | ```bash 40 | RUSTC_BOOTSTRAP=1 rustup run 1.87.0 cargo build --release 41 | ``` 42 | 43 | Note that by using normal `cargo` command RustOwl will be built with nightly compiler since there is a `rust-toolchain.toml` which specifies nightly compiler for development environment. 44 | 45 | ### VS Code extension 46 | 47 | For VS Code extension, we use `yarn` to setup environment. 48 | To get started, you have to install dependencies by running following command inside `vscode` directory: 49 | 50 | ```bash 51 | yarn install 52 | ``` 53 | 54 | ## Before submitting PR 55 | 56 | Before submitting PR, you have to check below: 57 | 58 | ### Rust code 59 | 60 | - Correctly formatted by `cargo fmt` 61 | - Linted using Clippy by `cargo clippy` 62 | 63 | ### VS Code extension 64 | 65 | - Correctly formatted by `yarn fmt` 66 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Reporting 2 | 3 | If you wish to report a security vulnerability but not in a public issue -- thank you! -- we ask that you follow the following process. 4 | 5 | Please report security vulnerabilities by filling out the following template: 6 | 7 | - PUBLIC: Please let us know if this vulnerability has been made or discussed publicly already, and if so, please let us know where. 8 | - DESCRIPTION: Please provide precise description of the security vulnerability you have found with as much information as you are able and willing to provide. 9 | 10 | Please send the above info, along with any other information you feel is pertinent to: . 11 | 12 | In addition, you may request that the project provide you a patched release in advance of the release announcement, however, we can not guarantee that such information will be provided to you in advance of the public release and announcement. 13 | 14 | However, I will email you at the same time the public announcement is made. 15 | We will let you know within a few weeks whether or not your report has been accepted or rejected. 16 | We ask that you please keep the report confidential until we have made a public announcement. 17 | 18 | -------------------------------------------------------------------------------- /docs/assets/readme-screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/readme-screenshot-2.png -------------------------------------------------------------------------------- /docs/assets/readme-screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/readme-screenshot-3.png -------------------------------------------------------------------------------- /docs/assets/readme-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/readme-screenshot.png -------------------------------------------------------------------------------- /docs/assets/rustowl-logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/assets/rustowl-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /docs/assets/vs-code-cursor-on-unwrap-visualized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/vs-code-cursor-on-unwrap-visualized.png -------------------------------------------------------------------------------- /docs/assets/vs-code-cursor-on-unwrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/vs-code-cursor-on-unwrap.png -------------------------------------------------------------------------------- /docs/assets/vs-code-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/docs/assets/vs-code-progress.png -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Here we describe how to install RustOwl command line tools. 4 | 5 | ## Using [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) 6 | 7 | One of the easiest way to install RustOwl is using cargo-binstall. 8 | 9 | ```bash 10 | cargo binstall rustowl 11 | ``` 12 | 13 | Toolchain is automatically Downloaded and unpacked. 14 | 15 | ## Donwload binary from release page 16 | 17 | Download only `rustowl` executable from [release page](https://github.com/cordx56/rustowl/releases/latest) and place it into the place you desire. 18 | Toolchain is automatically Downloaded and unpacked. 19 | 20 | ## Build from source 21 | 22 | Installing from source requires some tweaks and not recommended. 23 | There are bugs in executing the binary which is installed from source code. 24 | -------------------------------------------------------------------------------- /docs/lsp-spec.md: -------------------------------------------------------------------------------- 1 | # The RustOwl LSP specification 2 | 3 | `rustowl`, is an LSP server which provides RustOwl information. 4 | To display various types of decorations, RustOwl supports some custom methods from the client. 5 | 6 | Here, we describe the specifications of those custom methods. 7 | 8 | ## Types 9 | 10 | Here, we describe the types we will use in this document. 11 | 12 | ### `OprType` 13 | 14 | ```typescript 15 | "lifetime" | "imm_borrow" | "mut_borrow" | "move" | "call" | "outlive" | "shared_mut" 16 | ``` 17 | 18 | ### `Decoration` 19 | 20 |
{
21 |     "type": OprType,
22 |     "range": Range,
23 |     "hover_text": Option<String>,
24 |     "overlapped": bool
25 | }
26 | 
27 | 28 | `overlapped` field indicates that the decoration is overlapped and should be hidden. 29 | 30 | ## Methods 31 | 32 | We describe the custom methods used in RustOwl. 33 | 34 | ### `rustowl/cursor` 35 | 36 | #### Request payload 37 | 38 |
{
39 |     "position": Position,
40 |     "document": {
41 |         "uri": DocumentUri
42 |     }
43 | }
44 | 
45 | 46 | #### Response payload 47 | 48 |
{
49 |     "is_analyzed": bool,
50 |     "decorations": [Decoration]
51 | }
52 | 
53 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # RustOwl usage 2 | 3 | Here, I describe how to use RustOwl in your workflow. 4 | 5 | ## Getting started 6 | 7 | First, please install the RustOwl LSP server and extension for your editor. 8 | Installation guide is on [the top page of this repository](/README.md). 9 | 10 | Then, please open a Rust source code file (`.rs`) in the editor. 11 | RustOwl only works with a Cargo workspace, so you need to open the source code that is part of a Cargo workspace. 12 | I recommend you try RustOwl with a small, simple workspace first. 13 | RustOwl's analysis may take a long time for a large workspace. 14 | 15 | VS Code extension will automatically start analyzing the workspace. 16 | For other editors, you may need to enable RustOwl manually, but you can enable automatic loading in your configuration file. 17 | The progress of analysis will be shown in your editor. 18 | ![Progress](assets/vs-code-progress.png) 19 | 20 | After the analysis started, RustOwl waits for your request. 21 | Please place the text cursor on a variable or function call you would like to inspect. 22 | ![Cursor on unwrap](assets/vs-code-cursor-on-unwrap.png) 23 | RustOwl works for the analyzed portion, even if the entire analysis has not finished. 24 | If your program has some fatal errors (e.g., syntax errors or unrecoverable type errors), RustOwl cannot work for the part where the analysis failed. 25 | 26 | Wait for a few seconds, and then the ownership-related operations and lifetimes of the variable to which the `unwrap()` method call assigns a value will appear. 27 | ![unwrap visualized](assets/vs-code-cursor-on-unwrap-visualized.png) 28 | 29 | ## Basic usage 30 | 31 | Basically, RustOwl can be used to resolve ownership and lifetime errors. 32 | What RustOwl visualizes is: 33 | 34 | - Actual lifetime of variables 35 | - Shared (immutable) borrowing of variables 36 | - Mutable borrowing of variables 37 | - Value movement 38 | - Function call 39 | - Ownership and lifetime errors 40 | 41 | You can see which color is assigned to them on the top page of this repository. 42 | RustOwl can be used to see where a variable lives, where it dies, and where it is borrowed or moved. 43 | 44 | For VS Code, you can see the message that explains the meaning of the underline by hovering your mouse cursor over it. 45 | ![Hover message on VS Code](assets/readme-screenshot-3.png) 46 | 47 | This is the basic usage of RustOwl! 48 | Now you have a master's degree in RustOwl. 49 | 50 | ## Advanced usage 51 | 52 | The lifetime that RustOwl visualizes is the range of these variables: 53 | 54 | - Where the variable _lives_ 55 | - For the meaning of _NLL_ 56 | - Until the variable is _dropped_ or _moved_ 57 | - For the meaning of _RAII_ 58 | 59 | Based on this, we can use RustOwl as listed below: 60 | 61 | - To resolve _dead lock_ that is caused by some data structures like `Mutex` 62 | - Because these _locks_ are freed where the lock object is dropped 63 | - To manage resources 64 | - Like memories, files, and anything which is managed by _RAII_ respective 65 | 66 | Did you get a Ph.D. in lifetimes? 67 | So let's try managing resources with RustOwl. 68 | You will get a Ph.D. in RustOwl and computer resource management. 69 | -------------------------------------------------------------------------------- /ftplugin/rust.lua: -------------------------------------------------------------------------------- 1 | local config = require('rustowl.config') 2 | 3 | if not vim.g.loaded_rustowl then 4 | -- Plugin initialization (run only once) 5 | vim.g.loaded_rustowl = true 6 | 7 | local highlight_style = config.highlight_style or 'undercurl' 8 | 9 | local highlights = { 10 | lifetime = '#00cc00', 11 | imm_borrow = '#0000cc', 12 | mut_borrow = '#cc00cc', 13 | move = '#cccc00', 14 | call = '#cccc00', 15 | outlive = '#cc0000', 16 | } 17 | 18 | for hl_name, color in pairs(highlights) do 19 | local options = { default = true, sp = color } 20 | 21 | if highlight_style == 'underline' then 22 | options.underline = true 23 | else 24 | options.undercurl = true --Default config is undercurl 25 | end 26 | 27 | vim.api.nvim_set_hl(0, hl_name, options) 28 | end 29 | 30 | if config.auto_enable then 31 | require('rustowl.show-on-hover').enable_on_lsp_attach() 32 | end 33 | 34 | ---@enum rustowl.ClientCmd 35 | local RustowlCmd = { 36 | start_client = 'start_client', 37 | stop_client = 'stop_client', 38 | restart_client = 'restart_client', 39 | enable = 'enable', 40 | disable = 'disable', 41 | toggle = 'toggle', 42 | } 43 | 44 | local lsp = require('rustowl.lsp') 45 | local rustowl = require('rustowl') 46 | 47 | local function rustowl_user_cmd(opts) 48 | if vim.bo[0].filetype ~= 'rust' then 49 | vim.notify('Rustowl: Current buffer is not a rust file.', vim.log.levels.ERROR) 50 | return 51 | end 52 | local fargs = opts.fargs 53 | local cmd = fargs[1] 54 | ---@cast cmd rustowl.ClientCmd 55 | if cmd == RustowlCmd.start_client then 56 | lsp.start() 57 | elseif cmd == RustowlCmd.stop_client then 58 | lsp.stop() 59 | elseif cmd == RustowlCmd.restart_client then 60 | lsp.restart() 61 | elseif cmd == RustowlCmd.enable then 62 | rustowl.enable() 63 | elseif cmd == RustowlCmd.disable then 64 | rustowl.disable() 65 | elseif cmd == RustowlCmd.toggle then 66 | rustowl.toggle() 67 | end 68 | end 69 | 70 | vim.api.nvim_create_user_command('Rustowl', rustowl_user_cmd, { 71 | nargs = '+', 72 | desc = 'Starts, stops the rustowl LSP client', 73 | complete = function(arg_lead, cmdline, _) 74 | local clients = lsp.get_rustowl_clients() 75 | ---@type rustowl.ClientCmd[] 76 | local commands = {} 77 | if #clients == 0 then 78 | table.insert(commands, RustowlCmd.start_client) 79 | else 80 | table.insert(commands, RustowlCmd.toggle) 81 | if rustowl.is_enabled() then 82 | table.insert(commands, RustowlCmd.disable) 83 | else 84 | table.insert(commands, RustowlCmd.enable) 85 | end 86 | table.insert(commands, RustowlCmd.stop_client) 87 | table.insert(commands, RustowlCmd.restart_client) 88 | end 89 | if cmdline:match('^Rustowl%s+%w*$') then 90 | return vim.tbl_filter(function(command) 91 | return command:find(arg_lead) ~= nil 92 | end, commands) 93 | end 94 | end, 95 | }) 96 | end 97 | 98 | if config.auto_attach then 99 | require('rustowl.lsp').start() 100 | end 101 | -------------------------------------------------------------------------------- /lua/rustowl/config.lua: -------------------------------------------------------------------------------- 1 | ---NOTE: `require`ing this module initializes the config 2 | 3 | ---@class rustowl.Config 4 | --- 5 | ---Whether to auto-attach the LSP client when opening a Rust file. 6 | ---Default: `true` 7 | ---@field auto_attach? boolean 8 | --- 9 | ---Enable RustOwl immediately on LspAttach 10 | ---@field auto_enable? boolean 11 | --- 12 | ---Time in milliseconds to hover with the cursor before triggering RustOwl 13 | ---@field idle_time? number 14 | --- 15 | ---The LSP client config (This can also be set using |vim.lsp.config()|). 16 | ---@field client? rustowl.ClientConfig 17 | 18 | ---NOTE: This allows lua-language-server to provide users 19 | ---completions and hover when setting vim.g.rustowl directly. 20 | 21 | ---@type nil | rustowl.Config | fun():rustowl.Config 22 | vim.g.rustowl = vim.g.rustowl 23 | 24 | ---@class rustowl.ClientConfig: vim.lsp.ClientConfig 25 | --- 26 | ---A function for determining the root directory 27 | ---@field root_dir? fun():string()? 28 | 29 | ---Internal config (defaults), merged with the user config. 30 | ---@class rustowl.internal.Config 31 | local default_config = { 32 | ---@type boolean 33 | auto_attach = true, 34 | 35 | ---@type boolean 36 | auto_enable = false, 37 | 38 | ---@type number 39 | idle_time = 500, 40 | 41 | ---@type string 42 | highlight_style = 'undercurl', 43 | 44 | ---@class rustowl.internal.ClientConfig: vim.lsp.ClientConfig 45 | 46 | --- 47 | client = { 48 | 49 | ---@type string[] 50 | cmd = { 'rustowl' }, 51 | 52 | ---@type fun():string? 53 | root_dir = function() 54 | return vim.fs.root(0, { 'Cargo.toml', '.git' }) 55 | end, 56 | }, 57 | } 58 | 59 | local user_config = type(vim.g.rustowl) == 'function' and vim.g.rustowl() or vim.g.rustowl or {} 60 | 61 | ---@cast user_config rustowl.Config 62 | 63 | ---@type rustowl.Config 64 | local lsp_config = type(vim.lsp.config) == 'table' and vim.lsp.config.rustowl or {} 65 | 66 | ---@type rustowl.internal.Config 67 | local config = vim.tbl_deep_extend('force', default_config, user_config, lsp_config) 68 | 69 | vim.validate { 70 | auto_attach = { config.auto_attach, 'boolean' }, 71 | auto_enable = { config.auto_enable, 'boolean' }, 72 | idle_time = { config.idle_time, 'number' }, 73 | client = { config.client, { 'table' } }, 74 | highlight_style = { config.highlight_style, 'string' }, 75 | } 76 | 77 | -- validation for highlight_style to ensure undercurl or underline 78 | if config.highlight_style ~= 'undercurl' and config.highlight_style ~= 'underline' then 79 | vim.notify( 80 | "Rustowl: Invalid highlight_style '" .. config.highlight_style .. "'. Using default 'undercurl'.", 81 | vim.log.levels.WARN 82 | ) 83 | config.highlight_style = 'undercurl' 84 | end 85 | config.client.name = 'rustowl' 86 | 87 | return config 88 | -------------------------------------------------------------------------------- /lua/rustowl/highlight.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local hl_ns = vim.api.nvim_create_namespace('rustowl') 4 | 5 | ---@param line number 6 | ---@param col number 7 | ---@param bufnr? number 8 | function M.enable(line, col, bufnr) 9 | local lsp = require('rustowl.lsp') 10 | bufnr = bufnr or vim.api.nvim_get_current_buf() 11 | local clients = lsp.get_rustowl_clients { bufnr = bufnr } 12 | local params = { 13 | position = { line = line - 1, character = col }, 14 | document = vim.lsp.util.make_text_document_params(), 15 | } 16 | 17 | for _, client in ipairs(clients) do 18 | client:request('rustowl/cursor', params, function(_, result, _) 19 | if result ~= nil then 20 | for _, deco in ipairs(result['decorations']) do 21 | if not deco['overlapped'] then 22 | local start = { deco['range']['start']['line'], deco['range']['start']['character'] } 23 | local finish = { deco['range']['end']['line'], deco['range']['end']['character'] } 24 | local opts = { regtype = 'v', inclusive = true } 25 | vim.highlight.range(bufnr, hl_ns, deco['type'], start, finish, opts) 26 | end 27 | end 28 | end 29 | end, bufnr) 30 | end 31 | end 32 | 33 | ---@param bufnr? number 34 | function M.disable(bufnr) 35 | vim.api.nvim_buf_clear_namespace(bufnr or 0, hl_ns, 0, -1) 36 | end 37 | 38 | return M 39 | -------------------------------------------------------------------------------- /lua/rustowl/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- Enable RustOwl highlighting 4 | ---@param bufnr? number 5 | M.enable = function(bufnr) 6 | require('rustowl.show-on-hover').enable(bufnr) 7 | end 8 | 9 | --- Disable RustOwl highlighting 10 | ---@param bufnr? number 11 | M.disable = function(bufnr) 12 | require('rustowl.show-on-hover').disable(bufnr) 13 | end 14 | 15 | --- Toggle RustOwl highlighting on or off 16 | ---@param bufnr? number 17 | M.toggle = function(bufnr) 18 | require('rustowl.show-on-hover').toggle(bufnr) 19 | end 20 | 21 | ---@return true if rustowl highlighting is enabled 22 | M.is_enabled = function() 23 | return require('rustowl.show-on-hover').is_enabled() 24 | end 25 | 26 | ---@param opts? rustowl.Config 27 | function M.setup(opts) 28 | vim.g.rustowl = opts 29 | end 30 | 31 | return M 32 | -------------------------------------------------------------------------------- /lua/rustowl/lsp.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@param filter? vim.lsp.get_clients.Filter 4 | ---@return vim.lsp.Client[] 5 | M.get_rustowl_clients = function(filter) 6 | filter = vim.tbl_deep_extend('force', filter or {}, { 7 | name = 'rustowl', 8 | }) 9 | ---@diagnostic disable-next-line: deprecated 10 | return type(vim.lsp.get_clients) == 'function' and vim.lsp.get_clients(filter) or vim.lsp.get_active_clients(filter) 11 | end 12 | 13 | ---@param client vim.lsp.Client 14 | ---@param root_dir string 15 | ---@return boolean 16 | local function is_in_workspace(client, root_dir) 17 | if not client.workspace_folders then 18 | return false 19 | end 20 | for _, dir in ipairs(client.workspace_folders) do 21 | if (root_dir .. '/'):sub(1, #dir.name + 1) == dir.name .. '/' then 22 | return true 23 | end 24 | end 25 | return false 26 | end 27 | 28 | ---Compatibility for a breaking change in Nvim 0.11 29 | --- @param client vim.lsp.Client 30 | --- @param method string LSP method name. 31 | --- @param params table? LSP request params. 32 | --- @return boolean status indicating if the notification was successful. If it is false, then the client has shutdown. 33 | local function client_notify(client, method, params) 34 | local info = debug.getinfo(client.notify, 'u') 35 | if info.nparams > 0 then 36 | ---@diagnostic disable-next-line: param-type-mismatch 37 | return client:notify(method, params) 38 | else 39 | ---@diagnostic disable-next-line: param-type-mismatch 40 | return client.notify(method, params) 41 | end 42 | end 43 | 44 | ---@param client vim.lsp.Client 45 | ---@param root_dir string 46 | local function notify_workspace_folder(client, root_dir) 47 | if is_in_workspace(client, root_dir) then 48 | return 49 | end 50 | local workspace_folder = { uri = vim.uri_from_fname(root_dir), name = root_dir } 51 | local params = { 52 | event = { 53 | added = { workspace_folder }, 54 | removed = {}, 55 | }, 56 | } 57 | client_notify(client, 'workspace/didChangeWorkspaceFolders', params) 58 | if not client.workspace_folders then 59 | client.workspace_folders = {} 60 | end 61 | table.insert(client.workspace_folders, workspace_folder) 62 | end 63 | 64 | ---Start / attach the LSP client 65 | ---@return integer|nil client_id The LSP client ID 66 | M.start = function() 67 | local config = require('rustowl.config') 68 | local root_dir = config.client.root_dir() 69 | if not root_dir then 70 | vim.schedule(function() 71 | vim.notify('rustowl: Failed to detect root_dir.', vim.log.levels.ERROR) 72 | end) 73 | return 74 | end 75 | 76 | for _, client in ipairs(M.get_rustowl_clients()) do 77 | if not is_in_workspace(client, root_dir) then 78 | -- Attach to existing session 79 | notify_workspace_folder(client, root_dir) 80 | vim.lsp.buf_attach_client(0, client.id) 81 | return 82 | end 83 | end 84 | 85 | ---@param client vim.lsp.Client 86 | local notify_workspace_folder_hook = function(client) 87 | notify_workspace_folder(client, root_dir) 88 | end 89 | local on_init = type(config.client.on_init) == 'function' 90 | and function(...) 91 | notify_workspace_folder_hook(...) 92 | config.client.on_init(...) 93 | end 94 | or notify_workspace_folder_hook 95 | ---@type vim.lsp.ClientConfig 96 | local client_config = vim.tbl_deep_extend('force', config.client, { root_dir = root_dir, on_init = on_init }) 97 | return vim.lsp.start(client_config) 98 | end 99 | 100 | ---Compatibility for a breaking change in Nvim 0.11 101 | ---@param client vim.lsp.Client 102 | ---@return boolean 103 | local function client_is_stopped(client) 104 | local info = debug.getinfo(client.is_stopped, 'u') 105 | if info.nparams > 0 then 106 | ---@diagnostic disable-next-line: param-type-mismatch 107 | return client:is_stopped() 108 | else 109 | ---@diagnostic disable-next-line: missing-parameter 110 | return client.is_stopped() 111 | end 112 | end 113 | 114 | M.stop = function() 115 | local clients = M.get_rustowl_clients() 116 | vim.lsp.stop_client(clients) 117 | local t, err, _ = vim.uv.new_timer() 118 | local timer = assert(t, err) 119 | local max_attempts = 50 120 | local attempts_to_live = max_attempts 121 | local stopped_client_count = 0 122 | timer:start(200, 100, function() 123 | for _, client in ipairs(clients) do 124 | if client_is_stopped(client) then 125 | stopped_client_count = stopped_client_count + 1 126 | end 127 | end 128 | if stopped_client_count >= #clients then 129 | timer:stop() 130 | attempts_to_live = 0 131 | elseif attempts_to_live <= 0 then 132 | vim.schedule(function() 133 | vim.notify( 134 | ('rustowl: Could not stop all LSP clients after %d attempts.'):format(max_attempts), 135 | vim.log.levels.ERROR 136 | ) 137 | end) 138 | timer:stop() 139 | attempts_to_live = 0 140 | end 141 | attempts_to_live = attempts_to_live - 1 142 | end) 143 | end 144 | 145 | M.restart = function() 146 | M.stop() 147 | M.start() 148 | end 149 | 150 | return M 151 | -------------------------------------------------------------------------------- /lua/rustowl/show-on-hover.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local state = { 4 | augroup = nil, 5 | } 6 | 7 | function M.is_enabled() 8 | return state.augroup ~= nil 9 | end 10 | 11 | function M.enable_on_lsp_attach() 12 | local augroup = vim.api.nvim_create_augroup('RustOwlLspAttach', {}) 13 | 14 | vim.api.nvim_create_autocmd('LspAttach', { 15 | group = augroup, 16 | callback = function(event) 17 | M.enable(event.buf) 18 | end, 19 | }) 20 | end 21 | 22 | --- Enable RustOwl highlighting 23 | ---@param bufnr? number 24 | function M.enable(bufnr) 25 | local lsp = require('rustowl.lsp') 26 | if #lsp.get_rustowl_clients() == 0 then 27 | lsp.start() 28 | end 29 | local idle_time_ms = assert(require('rustowl.config').idle_time) 30 | 31 | local timer = nil 32 | 33 | local function clear_timer() 34 | if timer then 35 | timer:stop() 36 | timer:close() 37 | timer = nil 38 | end 39 | end 40 | 41 | local function start_timer() 42 | clear_timer() 43 | local t, err = vim.uv.new_timer() 44 | timer = t 45 | assert(timer, err) 46 | 47 | timer:start( 48 | idle_time_ms, 49 | 0, 50 | vim.schedule_wrap(function() 51 | local line, col = unpack(vim.api.nvim_win_get_cursor(0)) 52 | require('rustowl.highlight').enable(line, col, bufnr) 53 | end) 54 | ) 55 | end 56 | 57 | state.augroup = vim.api.nvim_create_augroup('RustOwl', { clear = true }) 58 | 59 | vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { 60 | group = state.augroup, 61 | buffer = bufnr, 62 | callback = function() 63 | require('rustowl.highlight').disable(bufnr) 64 | start_timer() 65 | end, 66 | }) 67 | 68 | vim.api.nvim_create_autocmd('BufUnload', { 69 | group = state.augroup, 70 | buffer = bufnr, 71 | callback = clear_timer, 72 | }) 73 | 74 | start_timer() 75 | end 76 | 77 | --- Disable RustOwl highlighting 78 | ---@param bufnr? number 79 | function M.disable(bufnr) 80 | require('rustowl.highlight').disable(bufnr) 81 | 82 | if M.is_enabled() then 83 | vim.api.nvim_del_augroup_by_id(state.augroup) 84 | end 85 | 86 | state.augroup = nil 87 | end 88 | 89 | --- Toggle RustOwl highlighting on or off 90 | ---@param bufnr? number 91 | function M.toggle(bufnr) 92 | local action = M.is_enabled() and M.disable or M.enable 93 | action(bufnr) 94 | end 95 | 96 | return M 97 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-04-08" 3 | components = ["rustc", "rust-std", "cargo", "rust-src", "rustc-dev", "llvm-tools"] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /rustowl.el: -------------------------------------------------------------------------------- 1 | ;;; rustowl.el --- Visualize Ownership and Lifetimes in Rust -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) cordx56 4 | 5 | ;; Author: cordx56 6 | ;; Keywords: tools 7 | 8 | ;; Version: 0.2.0 9 | ;; Package-Requires: ((emacs "24.1") (lsp-mode "9.0.0")) 10 | ;; URL: https://github.com/cordx56/rustowl 11 | 12 | ;; SPDX-License-Identifier: MPL-2.0 13 | 14 | ;;; Commentary: 15 | 16 | ;;; Code: 17 | 18 | (require 'lsp-mode) 19 | 20 | (defgroup rustowl () 21 | "Visualize Ownership and Lifetimes in Rust" 22 | :group 'tools 23 | :prefix "rustowl-" 24 | :link '(url-link "https://github.com/cordx56/rustowl")) 25 | 26 | ;;;###autoload 27 | (with-eval-after-load 'lsp-mode 28 | (lsp-register-client 29 | (make-lsp-client 30 | :new-connection (lsp-stdio-connection '("rustowl")) 31 | :major-modes '(rust-mode) 32 | :server-id 'rustowl 33 | :priority -1 34 | :add-on? t))) 35 | 36 | (defun rustowl-cursor (params) 37 | (lsp-request-async 38 | "rustowl/cursor" 39 | params 40 | (lambda (response) 41 | (let ((decorations (gethash "decorations" response))) 42 | (mapc 43 | (lambda (deco) 44 | (let* ((type (gethash "type" deco)) 45 | (start (gethash "start" (gethash "range" deco))) 46 | (end (gethash "end" (gethash "range" deco))) 47 | (start-pos 48 | (rustowl-line-col-to-pos 49 | (gethash "line" start) 50 | (gethash "character" start))) 51 | (end-pos 52 | (rustowl-line-col-to-pos 53 | (gethash "line" end) 54 | (gethash "character" end))) 55 | (overlapped (gethash "overlapped" deco))) 56 | (if (not overlapped) 57 | (cond 58 | ((equal type "lifetime") 59 | (rustowl-underline start-pos end-pos "#00cc00")) 60 | ((equal type "imm_borrow") 61 | (rustowl-underline start-pos end-pos "#0000cc")) 62 | ((equal type "mut_borrow") 63 | (rustowl-underline start-pos end-pos "#cc00cc")) 64 | ((or (equal type "move") (equal type "call")) 65 | (rustowl-underline start-pos end-pos "#cccc00")) 66 | ((equal type "outlive") 67 | (rustowl-underline start-pos end-pos "#cc0000")))))) 68 | decorations))) 69 | :mode 'current)) 70 | 71 | 72 | (defun rustowl-line-number-at-pos () 73 | (save-excursion 74 | (goto-char (point)) 75 | (count-lines (point-min) (line-beginning-position)))) 76 | (defun rustowl-current-column () 77 | (save-excursion 78 | (let ((start (point))) 79 | (move-beginning-of-line 1) 80 | (- start (point))))) 81 | 82 | (defun rustowl-cursor-call () 83 | (let ((line (rustowl-line-number-at-pos)) 84 | (column (rustowl-current-column)) 85 | (uri (lsp--buffer-uri))) 86 | (rustowl-cursor `( 87 | :position ,`( 88 | :line ,line 89 | :character ,column 90 | ) 91 | :document ,`( 92 | :uri ,uri 93 | ) 94 | )))) 95 | 96 | ;;;###autoload 97 | (defvar rustowl-cursor-timer nil) 98 | ;;;###autoload 99 | (defvar rustowl-cursor-timeout 2) 100 | 101 | ;;;###autoload 102 | (defun rustowl-reset-cursor-timer () 103 | (when rustowl-cursor-timer 104 | (cancel-timer rustowl-cursor-timer)) 105 | (rustowl-clear-overlays) 106 | (setq rustowl-cursor-timer 107 | (run-with-idle-timer rustowl-cursor-timeout nil #'rustowl-cursor-call))) 108 | 109 | ;;;###autoload 110 | (defun enable-rustowl-cursor () 111 | (add-hook 'post-command-hook #'rustowl-reset-cursor-timer)) 112 | 113 | ;;;###autoload 114 | (defun disable-rustowl-cursor () 115 | (remove-hook 'post-command-hook #'rustowl-reset-cursor-timer) 116 | (when rustowl-cursor-timer 117 | (cancel-timer rustowl-cursor-timer) 118 | (setq rustowl-cursor-timer nil))) 119 | 120 | ;; RustOwl visualization 121 | (defun rustowl-line-col-to-pos (line col) 122 | (save-excursion 123 | (goto-char (point-min)) 124 | (forward-line line) 125 | (move-to-column col) 126 | (point))) 127 | 128 | (defvar rustowl-overlays nil) 129 | 130 | (defun rustowl-underline (start end color) 131 | (let ((overlay (make-overlay start end))) 132 | (overlay-put overlay 'face `(:underline (:color ,color :style wave))) 133 | (push overlay rustowl-overlays) 134 | overlay)) 135 | 136 | (defun rustowl-clear-overlays () 137 | (interactive) 138 | (mapc #'delete-overlay rustowl-overlays) 139 | (setq rustowl-overlays nil)) 140 | 141 | (provide 'rustowl) 142 | ;;; rustowl.el ends here 143 | -------------------------------------------------------------------------------- /scripts/bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to update version numbers in multiple files and create a git tag 4 | # Usage: ./bump.sh v0.3.1 5 | 6 | # Ensure a version argument is provided 7 | if [ $# -ne 1 ]; then 8 | echo "Usage: $0 " 9 | echo "Example: $0 v0.3.1" 10 | exit 1 11 | fi 12 | 13 | [[ $(which gsed > /dev/null 2>&1; echo $?) = 0 ]] && sed="gsed" || sed="sed" 14 | 15 | VERSION=$1 16 | VERSION_WITHOUT_V="${VERSION#v}" 17 | 18 | echo "Updating to version: $VERSION" 19 | 20 | # Check if version contains alpha, beta, rc, dev, or other pre-release identifiers 21 | if echo "$VERSION_WITHOUT_V" | grep -q -E 'alpha|beta|rc|dev|pre|snapshot'; then 22 | IS_PRERELEASE=true 23 | echo "Pre-release version detected ($VERSION_WITHOUT_V). PKGBUILD will not be updated." 24 | else 25 | IS_PRERELEASE=false 26 | echo "Stable version detected ($VERSION_WITHOUT_V)." 27 | fi 28 | 29 | # 1. Update Cargo.toml in root directory (only the first version field) 30 | if [ -f Cargo.toml ]; then 31 | echo "Updating Cargo.toml..." 32 | # Use sed to replace only the first occurrence of the version line 33 | $sed -i '0,/^version = .*/{s/^version = .*/version = "'$VERSION_WITHOUT_V'"/}' Cargo.toml 34 | else 35 | echo "Error: Cargo.toml not found in current directory" 36 | exit 1 37 | fi 38 | 39 | # 2. Update vscode/package.json 40 | if [ -f vscode/package.json ]; then 41 | echo "Updating vscode/package.json..." 42 | # Use sed to replace the "version": "x.x.x" line 43 | $sed -i "s/\"version\": \".*\"/\"version\": \"$VERSION_WITHOUT_V\"/" vscode/package.json 44 | else 45 | echo "Warning: vscode/package.json not found" 46 | fi 47 | 48 | # 3. Update aur/PKGBUILD only for stable releases 49 | if [ "$IS_PRERELEASE" = false ] && [ -f aur/PKGBUILD ]; then 50 | echo "Updating aur/PKGBUILD..." 51 | # Use sed to replace the pkgver line 52 | $sed -i "s/^pkgver=.*/pkgver=$VERSION_WITHOUT_V/" aur/PKGBUILD 53 | elif [ -f aur/PKGBUILD ]; then 54 | echo "Skipping aur/PKGBUILD update for pre-release version" 55 | else 56 | echo "Warning: aur/PKGBUILD not found" 57 | fi 58 | 59 | # 4. Create a git tag 60 | echo "Creating git tag: $VERSION" 61 | git tag $VERSION 62 | 63 | echo "Version bump complete. Changes have been made to the files." 64 | echo "Remember to commit your changes before pushing the tag." 65 | echo "To push the tag: git push origin $VERSION" 66 | -------------------------------------------------------------------------------- /src/bin/core/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::await_holding_lock)] 2 | 3 | mod analyze; 4 | 5 | use analyze::MirAnalyzer; 6 | use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId}; 7 | use rustc_interface::interface; 8 | use rustc_middle::{ 9 | mir::BorrowCheckResult, query::queries::mir_borrowck::ProvidedValue, ty::TyCtxt, 10 | util::Providers, 11 | }; 12 | use rustc_session::config; 13 | use rustowl::models::*; 14 | use std::collections::HashMap; 15 | use std::env; 16 | use std::sync::{LazyLock, Mutex, atomic::AtomicBool}; 17 | use tokio::{ 18 | runtime::{Builder, Handle, Runtime}, 19 | task::JoinSet, 20 | }; 21 | 22 | pub struct RustcCallback; 23 | impl rustc_driver::Callbacks for RustcCallback {} 24 | 25 | static ATOMIC_TRUE: AtomicBool = AtomicBool::new(true); 26 | static TASKS: LazyLock>>> = 27 | LazyLock::new(|| Mutex::new(JoinSet::new())); 28 | static RUNTIME: LazyLock> = LazyLock::new(|| { 29 | Mutex::new( 30 | Builder::new_multi_thread() 31 | .enable_all() 32 | .worker_threads(8) 33 | .thread_stack_size(1024 * 1024 * 1024) 34 | .build() 35 | .unwrap(), 36 | ) 37 | }); 38 | static HANDLE: LazyLock = LazyLock::new(|| RUNTIME.lock().unwrap().handle().clone()); 39 | static ANALYZED: LazyLock>> = LazyLock::new(|| Mutex::new(Vec::new())); 40 | 41 | fn override_queries(_session: &rustc_session::Session, local: &mut Providers) { 42 | local.mir_borrowck = mir_borrowck; 43 | } 44 | fn mir_borrowck(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ProvidedValue<'_> { 45 | log::info!("start borrowck of {def_id:?}"); 46 | 47 | let analyzer = MirAnalyzer::new( 48 | unsafe { 49 | std::mem::transmute::, rustc_middle::ty::TyCtxt<'_>>(tcx) 50 | }, 51 | def_id, 52 | ); 53 | { 54 | let mut locked = TASKS.lock().unwrap(); 55 | locked.spawn_on(analyzer, &HANDLE); 56 | } 57 | let (current, mir_len) = { 58 | let mut locked = ANALYZED.lock().unwrap(); 59 | locked.push(def_id); 60 | let current = locked.len(); 61 | let mir_len = tcx 62 | .mir_keys(()) 63 | .into_iter() 64 | .filter(|v| tcx.hir_node_by_def_id(**v).body_id().is_some()) 65 | .count(); 66 | log::info!("borrow checked: {} / {}", current, mir_len); 67 | (current, mir_len) 68 | }; 69 | let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); 70 | if current == mir_len { 71 | RUNTIME.lock().unwrap().block_on(async move { 72 | while let Some(task) = { TASKS.lock().unwrap().join_next().await } { 73 | let (filename, analyzed) = task.unwrap().analyze(); 74 | log::info!("analyzed one item of {}", filename); 75 | let krate = Crate(HashMap::from([( 76 | filename, 77 | File { 78 | items: vec![analyzed], 79 | }, 80 | )])); 81 | let ws = Workspace(HashMap::from([(crate_name.clone(), krate)])); 82 | println!("{}", serde_json::to_string(&ws).unwrap()); 83 | } 84 | }) 85 | } 86 | 87 | let result = BorrowCheckResult { 88 | concrete_opaque_types: indexmap::IndexMap::default(), 89 | closure_requirements: None, 90 | used_mut_upvars: smallvec::SmallVec::new(), 91 | tainted_by_errors: None, 92 | }; 93 | 94 | tcx.arena.alloc(result) 95 | } 96 | 97 | pub struct AnalyzerCallback; 98 | impl rustc_driver::Callbacks for AnalyzerCallback { 99 | fn config(&mut self, config: &mut interface::Config) { 100 | config.using_internal_features = &ATOMIC_TRUE; 101 | config.opts.unstable_opts.mir_opt_level = Some(0); 102 | config.opts.unstable_opts.polonius = config::Polonius::Next; 103 | config.opts.incremental = None; 104 | config.override_queries = Some(override_queries); 105 | config.make_codegen_backend = None; 106 | } 107 | } 108 | 109 | pub fn run_compiler() -> i32 { 110 | let mut args: Vec = env::args().collect(); 111 | if args.first() == args.get(1) { 112 | args = args.into_iter().skip(1).collect(); 113 | } else { 114 | return rustc_driver::catch_with_exit_code(|| { 115 | rustc_driver::run_compiler(&args, &mut RustcCallback) 116 | }); 117 | } 118 | 119 | for arg in &args { 120 | if arg == "-vV" || arg == "--version" || arg.starts_with("--print") { 121 | return rustc_driver::catch_with_exit_code(|| { 122 | rustc_driver::run_compiler(&args, &mut RustcCallback) 123 | }); 124 | } 125 | } 126 | 127 | rustc_driver::catch_with_exit_code(|| { 128 | rustc_driver::run_compiler(&args, &mut AnalyzerCallback); 129 | }) 130 | } 131 | -------------------------------------------------------------------------------- /src/bin/rustowl.rs: -------------------------------------------------------------------------------- 1 | //! # RustOwl cargo-owlsp 2 | //! 3 | //! An LSP server for visualizing ownership and lifetimes in Rust, designed for debugging and optimization. 4 | 5 | use clap::{CommandFactory, Parser}; 6 | use clap_complete::generate; 7 | use rustowl::*; 8 | use std::env; 9 | use std::io; 10 | use tower_lsp::{LspService, Server}; 11 | 12 | use crate::cli::{Cli, Commands, ToolchainCommands}; 13 | 14 | fn set_log_level(default: log::LevelFilter) { 15 | log::set_max_level( 16 | env::var("RUST_LOG") 17 | .ok() 18 | .and_then(|v| v.parse().ok()) 19 | .unwrap_or(default), 20 | ); 21 | } 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | simple_logger::SimpleLogger::new() 26 | .with_colors(true) 27 | .init() 28 | .unwrap(); 29 | set_log_level("info".parse().unwrap()); 30 | 31 | let matches = Cli::parse(); 32 | if let Some(arg) = matches.command { 33 | match arg { 34 | Commands::Check(matches) => { 35 | let path = matches.path.unwrap_or(env::current_dir().unwrap()); 36 | if Backend::check(&path).await { 37 | log::info!("Successfully analyzed"); 38 | std::process::exit(0); 39 | } else { 40 | log::error!("Analyze failed"); 41 | std::process::exit(1); 42 | } 43 | } 44 | Commands::Clean => { 45 | if let Ok(meta) = cargo_metadata::MetadataCommand::new().exec() { 46 | let target = meta.target_directory.join("owl"); 47 | tokio::fs::remove_dir_all(&target).await.ok(); 48 | } 49 | } 50 | Commands::Toolchain(matches) => { 51 | if let Some(arg) = matches.command { 52 | match arg { 53 | ToolchainCommands::Install => { 54 | if toolchain::check_fallback_dir().is_none() 55 | && rustowl::toolchain::setup_toolchain().await.is_err() 56 | { 57 | std::process::exit(1); 58 | } 59 | } 60 | ToolchainCommands::Uninstall => { 61 | rustowl::toolchain::uninstall_toolchain().await; 62 | } 63 | } 64 | } 65 | } 66 | Commands::Completions(matches) => { 67 | set_log_level("off".parse().unwrap()); 68 | let shell = matches.shell; 69 | generate(shell, &mut Cli::command(), "rustowl", &mut io::stdout()); 70 | } 71 | } 72 | } else if matches.version { 73 | if matches.quiet == 0 { 74 | print!("RustOwl "); 75 | } 76 | println!("v{}", clap::crate_version!()); 77 | return; 78 | } else { 79 | set_log_level("warn".parse().unwrap()); 80 | eprintln!("RustOwl v{}", clap::crate_version!()); 81 | eprintln!("This is an LSP server. You can use --help flag to show help."); 82 | 83 | let stdin = tokio::io::stdin(); 84 | let stdout = tokio::io::stdout(); 85 | 86 | let (service, socket) = LspService::build(Backend::new) 87 | .custom_method("rustowl/cursor", Backend::cursor) 88 | .finish(); 89 | Server::new(stdin, stdout, socket).serve(service).await; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/bin/rustowlc.rs: -------------------------------------------------------------------------------- 1 | //! # RustOwl rustowlc 2 | //! 3 | //! A compiler implementation for visualizing ownership and lifetimes in Rust, designed for debugging and optimization. 4 | 5 | #![feature(rustc_private)] 6 | 7 | pub extern crate indexmap; 8 | pub extern crate polonius_engine; 9 | pub extern crate rustc_borrowck; 10 | pub extern crate rustc_driver; 11 | pub extern crate rustc_errors; 12 | pub extern crate rustc_hash; 13 | pub extern crate rustc_hir; 14 | pub extern crate rustc_index; 15 | pub extern crate rustc_interface; 16 | pub extern crate rustc_middle; 17 | pub extern crate rustc_session; 18 | pub extern crate rustc_span; 19 | pub extern crate smallvec; 20 | 21 | pub mod core; 22 | 23 | use std::process::exit; 24 | 25 | fn main() { 26 | // This is cited from [rustc](https://github.com/rust-lang/rust/blob/1.86.0/compiler/rustc/src/main.rs). 27 | // MIT License 28 | #[cfg(all(unix, feature = "jemalloc"))] 29 | { 30 | use std::os::raw::{c_int, c_void}; 31 | 32 | use tikv_jemalloc_sys as jemalloc_sys; 33 | 34 | #[used] 35 | static _F1: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::calloc; 36 | #[used] 37 | static _F2: unsafe extern "C" fn(*mut *mut c_void, usize, usize) -> c_int = 38 | jemalloc_sys::posix_memalign; 39 | #[used] 40 | static _F3: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::aligned_alloc; 41 | #[used] 42 | static _F4: unsafe extern "C" fn(usize) -> *mut c_void = jemalloc_sys::malloc; 43 | #[used] 44 | static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc; 45 | #[used] 46 | static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free; 47 | 48 | #[cfg(target_os = "macos")] 49 | { 50 | unsafe extern "C" { 51 | fn _rjem_je_zone_register(); 52 | } 53 | 54 | #[used] 55 | static _F7: unsafe extern "C" fn() = _rjem_je_zone_register; 56 | } 57 | } 58 | 59 | simple_logger::SimpleLogger::new() 60 | .env() 61 | .with_colors(true) 62 | .init() 63 | .unwrap(); 64 | exit(core::run_compiler()) 65 | } 66 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{ArgAction, Args, Parser, Subcommand, ValueHint}; 2 | 3 | #[derive(Debug, Parser)] 4 | #[command(author)] 5 | pub struct Cli { 6 | /// Print version. 7 | #[arg(short('V'), long)] 8 | pub version: bool, 9 | 10 | /// Suppress output. 11 | #[arg(short, long, action(ArgAction::Count))] 12 | pub quiet: u8, 13 | 14 | /// Use stdio to communicate with the LSP server. 15 | #[arg(long)] 16 | pub stdio: bool, 17 | 18 | #[command(subcommand)] 19 | pub command: Option, 20 | } 21 | 22 | #[derive(Debug, Subcommand)] 23 | pub enum Commands { 24 | /// Check availability. 25 | Check(Check), 26 | 27 | /// Remove artifacts from the target directory. 28 | Clean, 29 | 30 | /// Install or uninstall the toolchain. 31 | Toolchain(ToolchainArgs), 32 | 33 | /// Generate shell completions. 34 | Completions(Completions), 35 | } 36 | 37 | #[derive(Args, Debug)] 38 | pub struct Check { 39 | /// The path of a file or directory to check availability. 40 | #[arg(value_name("path"), value_hint(ValueHint::AnyPath))] 41 | pub path: Option, 42 | } 43 | 44 | #[derive(Args, Debug)] 45 | pub struct ToolchainArgs { 46 | #[command(subcommand)] 47 | pub command: Option, 48 | } 49 | 50 | #[derive(Debug, Subcommand)] 51 | pub enum ToolchainCommands { 52 | /// Install the toolchain. 53 | Install, 54 | 55 | /// Uninstall the toolchain. 56 | Uninstall, 57 | } 58 | 59 | #[derive(Args, Debug)] 60 | pub struct Completions { 61 | /// The shell to generate completions for. 62 | #[arg(value_enum)] 63 | pub shell: crate::shells::Shell, 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # RustOwl lib 2 | //! 3 | //! Libraries that used in RustOwl 4 | 5 | pub mod cli; 6 | pub mod lsp; 7 | pub mod models; 8 | pub mod shells; 9 | pub mod toolchain; 10 | pub mod utils; 11 | 12 | pub use lsp::backend::Backend; 13 | -------------------------------------------------------------------------------- /src/lsp.rs: -------------------------------------------------------------------------------- 1 | pub mod backend; 2 | pub mod decoration; 3 | pub mod progress; 4 | -------------------------------------------------------------------------------- /src/lsp/progress.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use tower_lsp::{Client, lsp_types}; 3 | 4 | #[derive(Serialize, Clone, Copy, PartialEq, Eq, Debug)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum AnalysisStatus { 7 | Analyzing, 8 | Finished, 9 | Error, 10 | } 11 | 12 | pub struct ProgressToken { 13 | client: Option, 14 | token: Option, 15 | } 16 | impl ProgressToken { 17 | pub async fn begin(client: Client, message: Option) -> Self { 18 | let token = lsp_types::NumberOrString::String(format!("{}", uuid::Uuid::new_v4())); 19 | client 20 | .send_request::( 21 | lsp_types::WorkDoneProgressCreateParams { 22 | token: token.clone(), 23 | }, 24 | ) 25 | .await 26 | .ok(); 27 | 28 | let value = lsp_types::ProgressParamsValue::WorkDone(lsp_types::WorkDoneProgress::Begin( 29 | lsp_types::WorkDoneProgressBegin { 30 | title: "RustOwl".to_owned(), 31 | cancellable: Some(false), 32 | message: message.map(|v| v.to_string()), 33 | percentage: Some(0), 34 | }, 35 | )); 36 | client 37 | .send_notification::(lsp_types::ProgressParams { 38 | token: token.clone(), 39 | value, 40 | }) 41 | .await; 42 | 43 | Self { 44 | client: Some(client), 45 | token: Some(token), 46 | } 47 | } 48 | pub async fn report(&self, message: Option, percentage: Option) { 49 | if let (Some(client), Some(token)) = (self.client.clone(), self.token.clone()) { 50 | let value = lsp_types::ProgressParamsValue::WorkDone( 51 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { 52 | cancellable: Some(false), 53 | message: message.map(|v| v.to_string()), 54 | percentage, 55 | }), 56 | ); 57 | client 58 | .send_notification::(lsp_types::ProgressParams { 59 | token, 60 | value, 61 | }) 62 | .await; 63 | } 64 | } 65 | pub async fn finish(mut self) { 66 | let value = lsp_types::ProgressParamsValue::WorkDone(lsp_types::WorkDoneProgress::End( 67 | lsp_types::WorkDoneProgressEnd { message: None }, 68 | )); 69 | if let (Some(client), Some(token)) = (self.client.take(), self.token.take()) { 70 | client 71 | .send_notification::(lsp_types::ProgressParams { 72 | token, 73 | value, 74 | }) 75 | .await; 76 | } 77 | } 78 | } 79 | impl Drop for ProgressToken { 80 | fn drop(&mut self) { 81 | let value = lsp_types::ProgressParamsValue::WorkDone(lsp_types::WorkDoneProgress::End( 82 | lsp_types::WorkDoneProgressEnd { message: None }, 83 | )); 84 | if let (Some(client), Some(token)) = (self.client.take(), self.token.take()) { 85 | tokio::spawn(async move { 86 | client 87 | .send_notification::( 88 | lsp_types::ProgressParams { token, value }, 89 | ) 90 | .await; 91 | }); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/models.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 7 | pub struct FnLocal { 8 | pub id: u32, 9 | pub fn_id: u32, 10 | } 11 | 12 | impl FnLocal { 13 | pub fn new(id: u32, fn_id: u32) -> Self { 14 | Self { id, fn_id } 15 | } 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 19 | #[serde(transparent)] 20 | pub struct Loc(pub u32); 21 | impl Loc { 22 | pub fn new(source: &str, byte_pos: u32, offset: u32) -> Self { 23 | let byte_pos = byte_pos.saturating_sub(offset); 24 | // it seems that the compiler is ignoring CR 25 | for (i, (byte, _)) in source.replace("\r", "").char_indices().enumerate() { 26 | if byte_pos <= byte as u32 { 27 | return Self(i as u32); 28 | } 29 | } 30 | Self(0) 31 | } 32 | } 33 | impl std::ops::Add for Loc { 34 | type Output = Loc; 35 | fn add(self, rhs: i32) -> Self::Output { 36 | if rhs < 0 && (self.0 as i32) < -rhs { 37 | Loc(0) 38 | } else { 39 | Loc(self.0 + rhs as u32) 40 | } 41 | } 42 | } 43 | impl std::ops::Sub for Loc { 44 | type Output = Loc; 45 | fn sub(self, rhs: i32) -> Self::Output { 46 | if 0 < rhs && (self.0 as i32) < rhs { 47 | Loc(0) 48 | } else { 49 | Loc(self.0 - rhs as u32) 50 | } 51 | } 52 | } 53 | impl From for Loc { 54 | fn from(value: u32) -> Self { 55 | Self(value) 56 | } 57 | } 58 | impl From for u32 { 59 | fn from(value: Loc) -> Self { 60 | value.0 61 | } 62 | } 63 | 64 | #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] 65 | pub struct Range { 66 | from: Loc, 67 | until: Loc, 68 | } 69 | impl Range { 70 | pub fn new(from: Loc, until: Loc) -> Option { 71 | if until.0 <= from.0 { 72 | None 73 | } else { 74 | Some(Self { from, until }) 75 | } 76 | } 77 | pub fn from(&self) -> Loc { 78 | self.from 79 | } 80 | pub fn until(&self) -> Loc { 81 | self.until 82 | } 83 | pub fn size(&self) -> u32 { 84 | self.until.0 - self.from.0 85 | } 86 | } 87 | 88 | #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] 89 | #[serde(rename_all = "snake_case", tag = "type")] 90 | pub enum MirVariable { 91 | User { 92 | index: u32, 93 | live: Range, 94 | dead: Range, 95 | }, 96 | Other { 97 | index: u32, 98 | live: Range, 99 | dead: Range, 100 | }, 101 | } 102 | 103 | #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] 104 | #[serde(transparent)] 105 | pub struct MirVariables(HashMap); 106 | impl Default for MirVariables { 107 | fn default() -> Self { 108 | Self::new() 109 | } 110 | } 111 | impl MirVariables { 112 | pub fn new() -> Self { 113 | Self(HashMap::new()) 114 | } 115 | pub fn push(&mut self, var: MirVariable) { 116 | match &var { 117 | MirVariable::User { index, .. } => { 118 | if !self.0.contains_key(index) { 119 | self.0.insert(*index, var); 120 | } 121 | } 122 | MirVariable::Other { index, .. } => { 123 | if !self.0.contains_key(index) { 124 | self.0.insert(*index, var); 125 | } 126 | } 127 | } 128 | } 129 | pub fn to_vec(self) -> Vec { 130 | self.0.into_values().collect() 131 | } 132 | } 133 | 134 | #[derive(Serialize, Deserialize, Clone, Debug)] 135 | #[serde(rename_all = "snake_case", tag = "type")] 136 | pub enum Item { 137 | Function { span: Range, mir: Function }, 138 | } 139 | 140 | #[derive(Serialize, Deserialize, Clone, Debug)] 141 | pub struct File { 142 | pub items: Vec, 143 | } 144 | 145 | #[derive(Serialize, Deserialize, Clone, Debug)] 146 | #[serde(transparent)] 147 | pub struct Workspace(pub HashMap); 148 | impl Workspace { 149 | pub fn merge(&mut self, other: Self) { 150 | let Workspace(crates) = other; 151 | for (name, krate) in crates { 152 | if let Some(insert) = self.0.get_mut(&name) { 153 | insert.merge(krate); 154 | } else { 155 | self.0.insert(name, krate); 156 | } 157 | } 158 | } 159 | } 160 | 161 | #[derive(Serialize, Deserialize, Clone, Debug)] 162 | #[serde(transparent)] 163 | pub struct Crate(pub HashMap); 164 | impl Crate { 165 | pub fn merge(&mut self, other: Self) { 166 | let Crate(files) = other; 167 | for (file, mir) in files { 168 | if let Some(insert) = self.0.get_mut(&file) { 169 | insert.items.extend_from_slice(&mir.items); 170 | insert.items.dedup_by(|a, b| a.fn_id == b.fn_id); 171 | } else { 172 | self.0.insert(file, mir); 173 | } 174 | } 175 | } 176 | } 177 | 178 | #[derive(Serialize, Deserialize, Clone, Debug)] 179 | #[serde(rename_all = "snake_case", tag = "type")] 180 | pub enum MirRval { 181 | Move { 182 | target_local: FnLocal, 183 | range: Range, 184 | }, 185 | Borrow { 186 | target_local: FnLocal, 187 | range: Range, 188 | mutable: bool, 189 | outlive: Option, 190 | }, 191 | } 192 | 193 | #[derive(Serialize, Deserialize, Clone, Debug)] 194 | #[serde(rename_all = "snake_case", tag = "type")] 195 | pub enum MirStatement { 196 | StorageLive { 197 | target_local: FnLocal, 198 | range: Range, 199 | }, 200 | StorageDead { 201 | target_local: FnLocal, 202 | range: Range, 203 | }, 204 | Assign { 205 | target_local: FnLocal, 206 | range: Range, 207 | rval: Option, 208 | }, 209 | } 210 | 211 | #[derive(Serialize, Deserialize, Clone, Debug)] 212 | #[serde(rename_all = "snake_case", tag = "type")] 213 | pub enum MirTerminator { 214 | Drop { 215 | local: FnLocal, 216 | range: Range, 217 | }, 218 | Call { 219 | destination_local: FnLocal, 220 | fn_span: Range, 221 | }, 222 | Other, 223 | } 224 | 225 | #[derive(Serialize, Deserialize, Clone, Debug)] 226 | pub struct MirBasicBlock { 227 | pub statements: Vec, 228 | pub terminator: Option, 229 | } 230 | 231 | #[derive(Serialize, Deserialize, Clone, Debug)] 232 | #[serde(tag = "type", rename_all = "snake_case")] 233 | pub enum MirDecl { 234 | User { 235 | local: FnLocal, 236 | name: String, 237 | span: Range, 238 | ty: String, 239 | lives: Vec, 240 | shared_borrow: Vec, 241 | mutable_borrow: Vec, 242 | drop: bool, 243 | drop_range: Vec, 244 | must_live_at: Vec, 245 | }, 246 | Other { 247 | local: FnLocal, 248 | ty: String, 249 | lives: Vec, 250 | shared_borrow: Vec, 251 | mutable_borrow: Vec, 252 | drop: bool, 253 | drop_range: Vec, 254 | must_live_at: Vec, 255 | }, 256 | } 257 | 258 | #[derive(Serialize, Deserialize, Clone, Debug)] 259 | pub struct Function { 260 | pub fn_id: u32, 261 | pub basic_blocks: Vec, 262 | pub decls: Vec, 263 | } 264 | -------------------------------------------------------------------------------- /src/shells.rs: -------------------------------------------------------------------------------- 1 | use clap_complete_nushell::Nushell; 2 | 3 | use std::fmt::Display; 4 | use std::path::Path; 5 | use std::str::FromStr; 6 | 7 | use clap::ValueEnum; 8 | 9 | use clap_complete::Generator; 10 | use clap_complete::shells; 11 | 12 | /// Shell with auto-generated completion script available. 13 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] 14 | #[non_exhaustive] 15 | #[value(rename_all = "lower")] 16 | pub enum Shell { 17 | /// Bourne Again `SHell` (bash) 18 | Bash, 19 | /// Elvish shell 20 | Elvish, 21 | /// Friendly Interactive `SHell` (fish) 22 | Fish, 23 | /// `PowerShell` 24 | PowerShell, 25 | /// Z `SHell` (zsh) 26 | Zsh, 27 | /// Nushell 28 | Nushell, 29 | } 30 | 31 | impl Display for Shell { 32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 | self.to_possible_value() 34 | .expect("no values are skipped") 35 | .get_name() 36 | .fmt(f) 37 | } 38 | } 39 | 40 | impl FromStr for Shell { 41 | type Err = String; 42 | 43 | fn from_str(s: &str) -> Result { 44 | for variant in Self::value_variants() { 45 | if variant.to_possible_value().unwrap().matches(s, false) { 46 | return Ok(*variant); 47 | } 48 | } 49 | Err(format!("invalid variant: {s}")) 50 | } 51 | } 52 | 53 | impl Generator for Shell { 54 | fn file_name(&self, name: &str) -> String { 55 | match self { 56 | Shell::Bash => shells::Bash.file_name(name), 57 | Shell::Elvish => shells::Elvish.file_name(name), 58 | Shell::Fish => shells::Fish.file_name(name), 59 | Shell::PowerShell => shells::PowerShell.file_name(name), 60 | Shell::Zsh => shells::Zsh.file_name(name), 61 | Shell::Nushell => Nushell.file_name(name), 62 | } 63 | } 64 | 65 | fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) { 66 | match self { 67 | Shell::Bash => shells::Bash.generate(cmd, buf), 68 | Shell::Elvish => shells::Elvish.generate(cmd, buf), 69 | Shell::Fish => shells::Fish.generate(cmd, buf), 70 | Shell::PowerShell => shells::PowerShell.generate(cmd, buf), 71 | Shell::Zsh => shells::Zsh.generate(cmd, buf), 72 | Shell::Nushell => Nushell.generate(cmd, buf), 73 | } 74 | } 75 | } 76 | 77 | impl Shell { 78 | /// Parse a shell from a path to the executable for the shell 79 | /// 80 | /// # Examples 81 | /// 82 | /// ``` 83 | /// use clap_complete::shells::Shell; 84 | /// 85 | /// assert_eq!(Shell::from_shell_path("/bin/bash"), Some(Shell::Bash)); 86 | /// assert_eq!(Shell::from_shell_path("/usr/bin/zsh"), Some(Shell::Zsh)); 87 | /// assert_eq!(Shell::from_shell_path("/opt/my_custom_shell"), None); 88 | /// ``` 89 | pub fn from_shell_path>(path: P) -> Option { 90 | parse_shell_from_path(path.as_ref()) 91 | } 92 | 93 | /// Determine the user's current shell from the environment 94 | /// 95 | /// This will read the SHELL environment variable and try to determine which shell is in use 96 | /// from that. 97 | /// 98 | /// If SHELL is not set, then on windows, it will default to powershell, and on 99 | /// other operating systems it will return `None`. 100 | /// 101 | /// If SHELL is set, but contains a value that doesn't correspond to one of the supported shell 102 | /// types, then return `None`. 103 | /// 104 | /// # Example: 105 | /// 106 | /// ```no_run 107 | /// # use clap::Command; 108 | /// use clap_complete::{generate, shells::Shell}; 109 | /// # fn build_cli() -> Command { 110 | /// # Command::new("compl") 111 | /// # } 112 | /// let mut cmd = build_cli(); 113 | /// generate(Shell::from_env().unwrap_or(Shell::Bash), &mut cmd, "myapp", &mut std::io::stdout()); 114 | /// ``` 115 | pub fn from_env() -> Option { 116 | if let Some(env_shell) = std::env::var_os("SHELL") { 117 | Shell::from_shell_path(env_shell) 118 | } else if cfg!(windows) { 119 | Some(Shell::PowerShell) 120 | } else { 121 | None 122 | } 123 | } 124 | } 125 | 126 | // use a separate function to avoid having to monomorphize the entire function due 127 | // to from_shell_path being generic 128 | fn parse_shell_from_path(path: &Path) -> Option { 129 | let name = path.file_stem()?.to_str()?; 130 | match name { 131 | "bash" => Some(Shell::Bash), 132 | "zsh" => Some(Shell::Zsh), 133 | "fish" => Some(Shell::Fish), 134 | "elvish" => Some(Shell::Elvish), 135 | "powershell" | "powershell_ise" => Some(Shell::PowerShell), 136 | "nushell" => Some(Shell::Nushell), 137 | _ => None, 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/toolchain.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::read_dir; 3 | use std::path::{Path, PathBuf}; 4 | use std::process::Stdio; 5 | use std::sync::LazyLock; 6 | use tokio::{ 7 | fs::{create_dir_all, remove_dir_all}, 8 | process, 9 | }; 10 | 11 | #[cfg(not(windows))] 12 | use flate2::read::GzDecoder; 13 | #[cfg(not(windows))] 14 | use tar::Archive; 15 | #[cfg(windows)] 16 | use zip::ZipArchive; 17 | 18 | pub const TOOLCHAIN: &str = env!("RUSTOWL_TOOLCHAIN"); 19 | 20 | static FALLBACK_RUNTIME_DIRS: LazyLock> = LazyLock::new(|| { 21 | let exec_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf(); 22 | vec![exec_dir.join("rustowl-runtime"), exec_dir] 23 | }); 24 | 25 | const BUILD_RUNTIME_DIRS: Option<&str> = option_env!("RUSTOWL_RUNTIME_DIRS"); 26 | static CONFIG_RUNTIME_DIRS: LazyLock> = LazyLock::new(|| { 27 | BUILD_RUNTIME_DIRS 28 | .map(|v| env::split_paths(v).collect()) 29 | .unwrap_or_default() 30 | }); 31 | const BUILD_SYSROOTS: Option<&str> = option_env!("RUSTOWL_SYSROOTS"); 32 | static CONFIG_SYSROOTS: LazyLock> = LazyLock::new(|| { 33 | BUILD_SYSROOTS 34 | .map(|v| env::split_paths(v).collect()) 35 | .unwrap_or_default() 36 | }); 37 | 38 | const ARCHIVE_NAME: &str = env!("RUSTOWL_ARCHIVE_NAME"); 39 | 40 | const RUSTC_DRIVER_NAME: &str = env!("RUSTC_DRIVER_NAME"); 41 | fn recursive_read_dir(path: impl AsRef) -> Vec { 42 | let mut paths = Vec::new(); 43 | if path.as_ref().is_dir() { 44 | for entry in read_dir(&path).unwrap().flatten() { 45 | let path = entry.path(); 46 | if path.is_dir() { 47 | paths.extend_from_slice(&recursive_read_dir(&path)); 48 | } else { 49 | paths.push(path); 50 | } 51 | } 52 | } 53 | paths 54 | } 55 | pub fn rustc_driver_path(sysroot: impl AsRef) -> Option { 56 | for file in recursive_read_dir(sysroot) { 57 | if file.file_name().unwrap().to_string_lossy() == RUSTC_DRIVER_NAME { 58 | log::info!("rustc_driver found: {}", file.display()); 59 | return Some(file); 60 | } 61 | } 62 | None 63 | } 64 | 65 | fn sysroot_from_runtime(runtime: impl AsRef) -> PathBuf { 66 | runtime.as_ref().join("sysroot").join(TOOLCHAIN) 67 | } 68 | fn is_valid_sysroot(sysroot: impl AsRef) -> bool { 69 | rustc_driver_path(sysroot).is_some() 70 | } 71 | fn get_configured_runtime_dir() -> Option { 72 | let env_var = env::var("RUSTOWL_RUNTIME_DIRS").unwrap_or_default(); 73 | 74 | for runtime in env::split_paths(&env_var) { 75 | if is_valid_sysroot(sysroot_from_runtime(&runtime)) { 76 | log::info!("select runtime dir from env var: {}", runtime.display()); 77 | return Some(runtime); 78 | } 79 | } 80 | 81 | for runtime in &*CONFIG_RUNTIME_DIRS { 82 | if is_valid_sysroot(sysroot_from_runtime(runtime)) { 83 | log::info!( 84 | "select runtime dir from build time env var: {}", 85 | runtime.display() 86 | ); 87 | return Some(runtime.clone()); 88 | } 89 | } 90 | None 91 | } 92 | pub fn check_fallback_dir() -> Option { 93 | for fallback in &*FALLBACK_RUNTIME_DIRS { 94 | if is_valid_sysroot(sysroot_from_runtime(fallback)) { 95 | log::info!("select runtime from fallback: {}", fallback.display()); 96 | return Some(fallback.clone()); 97 | } 98 | } 99 | None 100 | } 101 | async fn get_runtime_dir() -> PathBuf { 102 | if let Some(runtime) = get_configured_runtime_dir() { 103 | return runtime; 104 | } 105 | if let Some(fallback) = check_fallback_dir() { 106 | return fallback; 107 | } 108 | 109 | log::info!("rustc_driver not found; start setup toolchain"); 110 | match setup_toolchain().await { 111 | Ok(v) => v, 112 | Err(e) => { 113 | log::error!("{e:?}"); 114 | std::process::exit(1); 115 | } 116 | } 117 | } 118 | fn get_configured_sysroot() -> Option { 119 | let env_var = env::var("RUSTOWL_SYSROOTS").unwrap_or_default(); 120 | 121 | for sysroot in env::split_paths(&env_var) { 122 | if is_valid_sysroot(&sysroot) { 123 | log::info!("select sysroot dir from env var: {}", sysroot.display()); 124 | return Some(sysroot); 125 | } 126 | } 127 | 128 | for sysroot in &*CONFIG_SYSROOTS { 129 | if is_valid_sysroot(sysroot) { 130 | log::info!( 131 | "select sysroot dir from build time env var: {}", 132 | sysroot.display(), 133 | ); 134 | return Some(sysroot.clone()); 135 | } 136 | } 137 | None 138 | } 139 | pub async fn get_sysroot() -> PathBuf { 140 | if let Some(sysroot) = get_configured_sysroot() { 141 | return sysroot; 142 | } 143 | 144 | // get sysroot from rustup 145 | if let Ok(child) = process::Command::new("rustup") 146 | .args(["run", TOOLCHAIN, "rustc", "--print=sysroot"]) 147 | .stdout(Stdio::piped()) 148 | .spawn() 149 | { 150 | if let Ok(sysroot) = child 151 | .wait_with_output() 152 | .await 153 | .map(|v| PathBuf::from(String::from_utf8_lossy(&v.stdout).trim())) 154 | { 155 | if is_valid_sysroot(&sysroot) { 156 | log::info!( 157 | "select sysroot dir from rustup installed: {}", 158 | sysroot.display(), 159 | ); 160 | return sysroot; 161 | } 162 | } 163 | } 164 | 165 | // fallback sysroot 166 | sysroot_from_runtime(get_runtime_dir().await) 167 | } 168 | 169 | pub async fn setup_toolchain() -> Result { 170 | log::info!("start downloading {ARCHIVE_NAME}..."); 171 | let tarball_url = format!( 172 | "https://github.com/cordx56/rustowl/releases/download/v{}/{ARCHIVE_NAME}", 173 | clap::crate_version!(), 174 | ); 175 | 176 | let mut resp = match reqwest::get(&tarball_url) 177 | .await 178 | .and_then(|v| v.error_for_status()) 179 | { 180 | Ok(v) => v, 181 | Err(e) => { 182 | log::error!("failed to download runtime archive"); 183 | log::error!("{e:?}"); 184 | return Err(()); 185 | } 186 | }; 187 | 188 | let content_length = resp.content_length().unwrap_or(200_000_000) as usize; 189 | let mut data = Vec::with_capacity(content_length); 190 | let mut received = 0; 191 | while let Some(chunk) = match resp.chunk().await { 192 | Ok(v) => v, 193 | Err(e) => { 194 | log::error!("failed to download runtime archive"); 195 | log::error!("{e:?}"); 196 | return Err(()); 197 | } 198 | } { 199 | data.extend_from_slice(&chunk); 200 | let current = data.len() * 100 / content_length; 201 | if received != current { 202 | received = current; 203 | log::info!("{received:>3}% received"); 204 | } 205 | } 206 | log::info!("download finished"); 207 | 208 | let fallback = FALLBACK_RUNTIME_DIRS[0].clone(); 209 | if create_dir_all(&fallback).await.is_err() { 210 | log::error!("failed to create toolchain directory"); 211 | return Err(()); 212 | } 213 | 214 | #[cfg(windows)] 215 | { 216 | let cursor = std::io::Cursor::new(&*data); 217 | let mut archive = match ZipArchive::new(cursor) { 218 | Ok(archive) => archive, 219 | Err(e) => { 220 | log::error!("failed to read ZIP archive"); 221 | log::error!("{e:?}"); 222 | return Err(()); 223 | } 224 | }; 225 | 226 | for i in 0..archive.len() { 227 | let mut file = match archive.by_index(i) { 228 | Ok(file) => file, 229 | Err(e) => { 230 | log::error!("failed to read ZIP entry"); 231 | log::error!("{e:?}"); 232 | continue; 233 | } 234 | }; 235 | 236 | let outpath = match file.enclosed_name() { 237 | Some(path) => fallback.join(path), 238 | None => continue, 239 | }; 240 | 241 | if file.is_dir() { 242 | log::info!("File {} extracted to \"{}\"", i, outpath.display()); 243 | std::fs::create_dir_all(&outpath).unwrap(); 244 | } else { 245 | log::info!( 246 | "File {} extracted to \"{}\" ({} bytes)", 247 | i, 248 | outpath.display(), 249 | file.size() 250 | ); 251 | if let Some(p) = outpath.parent() { 252 | if !p.exists() { 253 | std::fs::create_dir_all(p).unwrap(); 254 | } 255 | } 256 | let mut outfile = std::fs::File::create(&outpath).unwrap(); 257 | std::io::copy(&mut file, &mut outfile).unwrap(); 258 | } 259 | 260 | log::info!("{} unpacked", outpath.display()); 261 | } 262 | } 263 | 264 | #[cfg(not(windows))] 265 | { 266 | let decoder = GzDecoder::new(&*data); 267 | let mut archive = Archive::new(decoder); 268 | if let Ok(entries) = archive.entries() { 269 | for mut entry in entries.flatten() { 270 | if let Ok(path) = entry.path() { 271 | let path = path.to_path_buf(); 272 | if path.as_os_str() != "rustowl" { 273 | if !entry.unpack_in(&fallback).unwrap_or(false) { 274 | log::error!("failed to unpack runtime tarball"); 275 | return Err(()); 276 | } 277 | log::info!("{} unpacked", path.display()); 278 | } 279 | } 280 | } 281 | } else { 282 | log::error!("failed to unpack runtime tarball"); 283 | return Err(()); 284 | } 285 | } 286 | 287 | log::info!("runtime setup done in {}", fallback.display()); 288 | Ok(fallback) 289 | } 290 | 291 | pub async fn uninstall_toolchain() { 292 | for fallback in &*FALLBACK_RUNTIME_DIRS { 293 | let sysroot = sysroot_from_runtime(fallback); 294 | if sysroot.is_dir() { 295 | log::info!("remove sysroot: {}", sysroot.display()); 296 | remove_dir_all(&sysroot).await.unwrap(); 297 | } 298 | } 299 | } 300 | 301 | pub async fn get_rustowlc_path() -> String { 302 | let mut current_rustowlc = env::current_exe().unwrap(); 303 | #[cfg(not(windows))] 304 | current_rustowlc.set_file_name("rustowlc"); 305 | #[cfg(windows)] 306 | current_rustowlc.set_file_name("rustowlc.exe"); 307 | if current_rustowlc.is_file() { 308 | log::info!("rustowlc is selected in the same directory as rustowl executable"); 309 | return current_rustowlc.to_string_lossy().to_string(); 310 | } 311 | 312 | if process::Command::new("rustowlc").spawn().is_ok() { 313 | log::info!("rustowlc is selected in PATH"); 314 | return "rustowlc".to_owned(); 315 | } 316 | 317 | let runtime_dir = get_runtime_dir().await; 318 | #[cfg(not(windows))] 319 | let rustowlc = runtime_dir.join("rustowlc"); 320 | #[cfg(windows)] 321 | let rustowlc = runtime_dir.join("rustowlc.exe"); 322 | if rustowlc.is_file() { 323 | rustowlc.to_string_lossy().to_string() 324 | } else { 325 | log::warn!("rustowlc not found; fallback"); 326 | "rustowlc".to_owned() 327 | } 328 | } 329 | 330 | pub fn set_rustc_env(command: &mut tokio::process::Command, sysroot: &Path) { 331 | command 332 | .env("RUSTC_BOOTSTRAP", "1") // Support nightly projects 333 | .env( 334 | "CARGO_ENCODED_RUSTFLAGS", 335 | format!("--sysroot={}", sysroot.display()), 336 | ); 337 | 338 | let driver_dir = rustc_driver_path(sysroot) 339 | .unwrap() 340 | .parent() 341 | .unwrap() 342 | .to_path_buf(); 343 | 344 | #[cfg(target_os = "linux")] 345 | { 346 | let mut paths = env::split_paths(&env::var("LD_LIBRARY_PATH").unwrap_or("".to_owned())) 347 | .collect::>(); 348 | paths.push_front(sysroot.join(driver_dir)); 349 | let paths = env::join_paths(paths).unwrap(); 350 | command.env("LD_LIBRARY_PATH", paths); 351 | } 352 | #[cfg(target_os = "macos")] 353 | { 354 | let mut paths = 355 | env::split_paths(&env::var("DYLD_FALLBACK_LIBRARY_PATH").unwrap_or("".to_owned())) 356 | .collect::>(); 357 | paths.push_front(sysroot.join(driver_dir)); 358 | let paths = env::join_paths(paths).unwrap(); 359 | command.env("DYLD_FALLBACK_LIBRARY_PATH", paths); 360 | } 361 | #[cfg(target_os = "windows")] 362 | { 363 | let mut paths = env::split_paths(&env::var_os("Path").unwrap()) 364 | .collect::>(); 365 | paths.push_front(sysroot.join(driver_dir)); 366 | let paths = env::join_paths(paths).unwrap(); 367 | command.env("Path", paths); 368 | } 369 | 370 | #[cfg(unix)] 371 | unsafe { 372 | command.pre_exec(|| { 373 | libc::setsid(); 374 | Ok(()) 375 | }); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::models::*; 2 | 3 | pub fn is_super_range(r1: Range, r2: Range) -> bool { 4 | (r1.from() < r2.from() && r2.until() <= r1.until()) 5 | || (r1.from() <= r2.from() && r2.until() < r1.until()) 6 | } 7 | 8 | pub fn common_range(r1: Range, r2: Range) -> Option { 9 | if r2.from() < r1.from() { 10 | return common_range(r2, r1); 11 | } 12 | if r1.until() < r2.from() { 13 | return None; 14 | } 15 | let from = r2.from(); 16 | let until = r1.until().min(r2.until()); 17 | Range::new(from, until) 18 | } 19 | 20 | pub fn common_ranges(ranges: &[Range]) -> Vec { 21 | let mut common_ranges = Vec::new(); 22 | for i in 0..ranges.len() { 23 | for j in i + 1..ranges.len() { 24 | if let Some(common) = common_range(ranges[i], ranges[j]) { 25 | common_ranges.push(common); 26 | } 27 | } 28 | } 29 | eliminated_ranges(common_ranges) 30 | } 31 | 32 | /// merge two ranges, result is superset of two ranges 33 | pub fn merge_ranges(r1: Range, r2: Range) -> Option { 34 | if common_range(r1, r2).is_some() || r1.until() == r2.from() || r2.until() == r1.from() { 35 | let from = r1.from().min(r2.from()); 36 | let until = r1.until().max(r2.until()); 37 | Range::new(from, until) 38 | } else { 39 | None 40 | } 41 | } 42 | 43 | /// eliminate common ranges and flatten ranges 44 | pub fn eliminated_ranges(mut ranges: Vec) -> Vec { 45 | let mut i = 0; 46 | 'outer: while i < ranges.len() { 47 | let mut j = 0; 48 | while j < ranges.len() { 49 | if i != j { 50 | if let Some(merged) = merge_ranges(ranges[i], ranges[j]) { 51 | ranges[i] = merged; 52 | ranges.remove(j); 53 | continue 'outer; 54 | } 55 | } 56 | j += 1; 57 | } 58 | i += 1; 59 | } 60 | ranges 61 | } 62 | 63 | pub fn exclude_ranges(mut from: Vec, excludes: Vec) -> Vec { 64 | let mut i = 0; 65 | 'outer: while i < from.len() { 66 | let mut j = 0; 67 | while j < excludes.len() { 68 | if let Some(common) = common_range(from[i], excludes[j]) { 69 | if let Some(r) = Range::new(from[i].from(), common.from() - 1) { 70 | from.push(r); 71 | } 72 | if let Some(r) = Range::new(common.until() + 1, from[i].until()) { 73 | from.push(r); 74 | } 75 | from.remove(i); 76 | continue 'outer; 77 | } 78 | j += 1; 79 | } 80 | i += 1; 81 | } 82 | eliminated_ranges(from) 83 | } 84 | 85 | #[allow(unused)] 86 | pub trait MirVisitor { 87 | fn visit_func(&mut self, func: &Function) {} 88 | fn visit_decl(&mut self, decl: &MirDecl) {} 89 | fn visit_stmt(&mut self, stmt: &MirStatement) {} 90 | fn visit_term(&mut self, term: &MirTerminator) {} 91 | } 92 | pub fn mir_visit(func: &Function, visitor: &mut impl MirVisitor) { 93 | visitor.visit_func(func); 94 | for decl in &func.decls { 95 | visitor.visit_decl(decl); 96 | } 97 | for bb in &func.basic_blocks { 98 | for stmt in &bb.statements { 99 | visitor.visit_stmt(stmt); 100 | } 101 | if let Some(term) = &bb.terminator { 102 | visitor.visit_term(term); 103 | } 104 | } 105 | } 106 | 107 | pub fn index_to_line_char(s: &str, idx: Loc) -> (u32, u32) { 108 | let mut line = 0; 109 | let mut col = 0; 110 | // it seems that the compiler is ignoring CR 111 | for (i, c) in s.replace("\r", "").chars().enumerate() { 112 | if idx == Loc::from(i as u32) { 113 | return (line, col); 114 | } 115 | if c == '\n' { 116 | line += 1; 117 | col = 0; 118 | } else if c != '\r' { 119 | col += 1; 120 | } 121 | } 122 | (0, 0) 123 | } 124 | pub fn line_char_to_index(s: &str, mut line: u32, char: u32) -> u32 { 125 | let mut col = 0; 126 | // it seems that the compiler is ignoring CR 127 | for (i, c) in s.replace("\r", "").chars().enumerate() { 128 | if line == 0 && col == char { 129 | return i as u32; 130 | } 131 | if c == '\n' && 0 < line { 132 | line -= 1; 133 | col = 0; 134 | } else if c != '\r' { 135 | col += 1; 136 | } 137 | } 138 | 0 139 | } 140 | -------------------------------------------------------------------------------- /vscode/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /vscode/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] 5 | } 6 | -------------------------------------------------------------------------------- /vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /vscode/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files 5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off" 13 | } -------------------------------------------------------------------------------- /vscode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "watch", 8 | "dependsOn": [ 9 | "npm: watch:tsc", 10 | "npm: watch:esbuild" 11 | ], 12 | "presentation": { 13 | "reveal": "never" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch:esbuild", 23 | "group": "build", 24 | "problemMatcher": "$esbuild-watch", 25 | "isBackground": true, 26 | "label": "npm: watch:esbuild", 27 | "presentation": { 28 | "group": "watch", 29 | "reveal": "never" 30 | } 31 | }, 32 | { 33 | "type": "npm", 34 | "script": "watch:tsc", 35 | "group": "build", 36 | "problemMatcher": "$tsc-watch", 37 | "isBackground": true, 38 | "label": "npm: watch:tsc", 39 | "presentation": { 40 | "group": "watch", 41 | "reveal": "never" 42 | } 43 | }, 44 | { 45 | "type": "npm", 46 | "script": "watch-tests", 47 | "problemMatcher": "$tsc-watch", 48 | "isBackground": true, 49 | "presentation": { 50 | "reveal": "never", 51 | "group": "watchers" 52 | }, 53 | "group": "build" 54 | }, 55 | { 56 | "label": "tasks: watch-tests", 57 | "dependsOn": [ 58 | "npm: watch", 59 | "npm: watch-tests" 60 | ], 61 | "problemMatcher": [] 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | esbuild.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/eslint.config.mjs 12 | **/*.map 13 | **/*.ts 14 | **/.vscode-test.* 15 | -------------------------------------------------------------------------------- /vscode/.yarnrc: -------------------------------------------------------------------------------- 1 | --ignore-engines true -------------------------------------------------------------------------------- /vscode/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /vscode/README.md: -------------------------------------------------------------------------------- 1 | # RustOwl VSCode 2 | 3 | This is RustOwl extension for VSCode. 4 | 5 | ## Installing RustOwl 6 | 7 | Before using RustOwl VSCode, you need to install RustOwl. 8 | For detail, refer the [document](https://github.com/cordx56/rustowl/blob/main/README.md). 9 | -------------------------------------------------------------------------------- /vscode/esbuild.js: -------------------------------------------------------------------------------- 1 | const esbuild = require("esbuild"); 2 | 3 | const production = process.argv.includes('--production'); 4 | const watch = process.argv.includes('--watch'); 5 | 6 | /** 7 | * @type {import('esbuild').Plugin} 8 | */ 9 | const esbuildProblemMatcherPlugin = { 10 | name: 'esbuild-problem-matcher', 11 | 12 | setup(build) { 13 | build.onStart(() => { 14 | console.log('[watch] build started'); 15 | }); 16 | build.onEnd((result) => { 17 | result.errors.forEach(({ text, location }) => { 18 | console.error(`✘ [ERROR] ${text}`); 19 | console.error(` ${location.file}:${location.line}:${location.column}:`); 20 | }); 21 | console.log('[watch] build finished'); 22 | }); 23 | }, 24 | }; 25 | 26 | async function main() { 27 | const ctx = await esbuild.context({ 28 | entryPoints: [ 29 | 'src/extension.ts' 30 | ], 31 | bundle: true, 32 | format: 'cjs', 33 | minify: production, 34 | sourcemap: !production, 35 | sourcesContent: false, 36 | platform: 'node', 37 | outfile: 'dist/extension.js', 38 | external: ['vscode'], 39 | logLevel: 'silent', 40 | plugins: [ 41 | /* add to the end of plugins array */ 42 | esbuildProblemMatcherPlugin, 43 | ], 44 | }); 45 | if (watch) { 46 | await ctx.watch(); 47 | } else { 48 | await ctx.rebuild(); 49 | await ctx.dispose(); 50 | } 51 | } 52 | 53 | main().catch(e => { 54 | console.error(e); 55 | process.exit(1); 56 | }); 57 | -------------------------------------------------------------------------------- /vscode/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | 4 | export default [{ 5 | files: ["**/*.ts"], 6 | }, { 7 | plugins: { 8 | "@typescript-eslint": typescriptEslint, 9 | }, 10 | 11 | languageOptions: { 12 | parser: tsParser, 13 | ecmaVersion: 2022, 14 | sourceType: "module", 15 | }, 16 | 17 | rules: { 18 | "@typescript-eslint/naming-convention": ["warn", { 19 | selector: "import", 20 | format: ["camelCase", "PascalCase"], 21 | }], 22 | 23 | curly: "warn", 24 | eqeqeq: "warn", 25 | "no-throw-literal": "warn", 26 | semi: "warn", 27 | }, 28 | }]; -------------------------------------------------------------------------------- /vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rustowl-vscode", 3 | "displayName": "RustOwl VSCode", 4 | "description": "Visualize ownership and lifetimes in Rust", 5 | "version": "0.3.4", 6 | "author": "cordx56 ", 7 | "publisher": "cordx56", 8 | "engines": { 9 | "vscode": "^1.100.0" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/cordx56/rustowl.git" 14 | }, 15 | "license": "MPL-2.0", 16 | "keywords": [ 17 | "rust", 18 | "ownership", 19 | "lifetimes", 20 | "visualization" 21 | ], 22 | "categories": [ 23 | "Programming Languages" 24 | ], 25 | "activationEvents": [ 26 | "onLanguage:rust" 27 | ], 28 | "icon": "rustowl-icon.png", 29 | "contributes": { 30 | "configuration": { 31 | "title": "rustowl", 32 | "properties": { 33 | "rustowl.underlineThickness": { 34 | "type": "string", 35 | "default": "2", 36 | "enum": [ 37 | "1", 38 | "2", 39 | "3", 40 | "4" 41 | ], 42 | "description": "The stroke thickness of the underline line" 43 | }, 44 | "rustowl.lifetimeColor": { 45 | "type": "string", 46 | "default": "hsla(125, 80%, 60%, 0.6)", 47 | "description": "The color of the lifetime underline" 48 | }, 49 | "rustowl.moveCallColor": { 50 | "type": "string", 51 | "default": "hsla(35, 80%, 60%, 0.6)", 52 | "description": "The color of the move/call underline" 53 | }, 54 | "rustowl.immutableBorrowColor": { 55 | "type": "string", 56 | "default": "hsla(230, 80%, 60%, 0.6)", 57 | "description": "The color of the immutable borrow underline" 58 | }, 59 | "rustowl.mutableBorrowColor": { 60 | "type": "string", 61 | "default": "hsla(300, 80%, 60%, 0.6)", 62 | "description": "The color of the mutable borrow underline" 63 | }, 64 | "rustowl.outliveColor": { 65 | "type": "string", 66 | "default": "hsla(0, 80%, 60%, 0.6)", 67 | "description": "The color of the outlive underline" 68 | }, 69 | "rustowl.displayDelay": { 70 | "type": "number", 71 | "default": 2000, 72 | "description": "Delay in displaying underlines (ms)" 73 | } 74 | } 75 | } 76 | }, 77 | "main": "./dist/extension.js", 78 | "scripts": { 79 | "vscode:prepublish": "yarn run package", 80 | "compile": "yarn run check-types && yarn run lint && node esbuild.js", 81 | "watch": "npm-run-all -p watch:*", 82 | "watch:esbuild": "node esbuild.js --watch", 83 | "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", 84 | "package": "yarn run check-types && yarn run lint && node esbuild.js --production", 85 | "compile-tests": "tsc -p . --outDir out", 86 | "watch-tests": "tsc -p . -w --outDir out", 87 | "pretest": "yarn run compile-tests && yarn run compile && yarn run lint", 88 | "check-types": "tsc --noEmit", 89 | "lint": "eslint src", 90 | "test": "vscode-test", 91 | "fmt": "prettier -w src", 92 | "build": "vsce package" 93 | }, 94 | "devDependencies": { 95 | "@types/mocha": "^10.0.9", 96 | "@types/node": "^22.15.29", 97 | "@types/vscode": "^1.100.0", 98 | "@typescript-eslint/eslint-plugin": "^8.33.0", 99 | "@typescript-eslint/parser": "^8.33.0", 100 | "@vscode/test-cli": "^0.0.11", 101 | "@vscode/test-electron": "^2.5.2", 102 | "@vscode/vsce": "^3.4.2", 103 | "esbuild": "^0.25.5", 104 | "eslint": "^9.28.0", 105 | "npm-run-all": "^4.1.5", 106 | "prettier": "^3.3.3", 107 | "typescript": "^5.8.3" 108 | }, 109 | "dependencies": { 110 | "semver-parser": "^4.1.8", 111 | "vscode-languageclient": "^9.0.1", 112 | "zod": "^3.25.47" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /vscode/rustowl-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cordx56/rustowl/78ccb89b94a431cf76fd998f1811442553738916/vscode/rustowl-icon.png -------------------------------------------------------------------------------- /vscode/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import { spawn, spawnSync } from "node:child_process"; 3 | import * as vscode from "vscode"; 4 | const version = require("../package.json").version as string; 5 | 6 | export const hostTuple = (): string | null => { 7 | let arch = null; 8 | if (process.arch === "arm64") { 9 | arch = "aarch64"; 10 | } else if (process.arch === "x64") { 11 | arch = "x86_64"; 12 | } 13 | let platform = null; 14 | if (process.platform === "linux") { 15 | platform = "unknown-linux-gnu"; 16 | } else if (process.platform === "darwin") { 17 | platform = "apple-darwin"; 18 | } else if (process.platform === "win32") { 19 | platform = "pc-windows-msvc"; 20 | } 21 | if (arch && platform) { 22 | return `${arch}-${platform}`; 23 | } else { 24 | return null; 25 | } 26 | }; 27 | 28 | const exeExt = hostTuple()?.includes("windows") ? ".exe" : ""; 29 | 30 | export const downloadRustowl = async (basePath: string) => { 31 | const baseUrl = `https://github.com/cordx56/rustowl/releases/download/v${version}`; 32 | const host = hostTuple(); 33 | if (host) { 34 | const owl = await fetch(`${baseUrl}/rustowl-${host}${exeExt}`); 35 | if (owl.status !== 200) { 36 | throw Error("RustOwl download error"); 37 | } 38 | await fs.writeFile( 39 | `${basePath}/rustowl${exeExt}`, 40 | Buffer.from(await owl.arrayBuffer()), 41 | { flag: "w" }, 42 | ); 43 | fs.chmod(`${basePath}/rustowl${exeExt}`, "755"); 44 | } else { 45 | throw Error("unsupported architecture or platform"); 46 | } 47 | }; 48 | 49 | const exists = async (path: string) => { 50 | return fs 51 | .access(path) 52 | .then(() => true) 53 | .catch(() => false); 54 | }; 55 | const needUpdated = async (currentVersion: string) => { 56 | if (!currentVersion) { 57 | return true; 58 | } 59 | console.log(`current RustOwl version: ${currentVersion.trim()}`); 60 | console.log(`extension version: v${version}`); 61 | try { 62 | const semverParser = await import("semver-parser"); 63 | const current = semverParser.parseSemVer(currentVersion.trim(), false); 64 | const self = semverParser.parseSemVer(version, false); 65 | if ( 66 | current.major === self.major && 67 | current.minor === self.minor && 68 | JSON.stringify(current.pre) === JSON.stringify(self.pre) 69 | ) { 70 | return false; 71 | } else { 72 | return true; 73 | } 74 | } catch (_e) { 75 | return true; 76 | } 77 | }; 78 | const getRustowlCommand = async (dirPath: string) => { 79 | const rustowlPath = `${dirPath}/rustowl${exeExt}`; 80 | if (spawnSync("rustowl", ["--version", "--quiet"]).stdout?.toString()) { 81 | return "rustowl"; 82 | } else if ( 83 | (await exists(rustowlPath)) && 84 | spawnSync(rustowlPath, ["--version", "--quiet"]).stdout?.toString() 85 | ) { 86 | return rustowlPath; 87 | } else { 88 | return null; 89 | } 90 | }; 91 | export const bootstrapRustowl = async (dirPath: string): Promise => { 92 | let rustowlCommand = await getRustowlCommand(dirPath); 93 | if ( 94 | !rustowlCommand || 95 | (await needUpdated( 96 | spawnSync(rustowlCommand, ["--version", "--quiet"]).stdout?.toString(), 97 | )) 98 | ) { 99 | await fs.mkdir(dirPath, { recursive: true }); 100 | // download rustowl binary 101 | await vscode.window.withProgress( 102 | { 103 | location: vscode.ProgressLocation.Notification, 104 | title: "RustOwl installing", 105 | cancellable: false, 106 | }, 107 | async (progress) => { 108 | try { 109 | progress.report({ message: "binary downloading" }); 110 | await downloadRustowl(dirPath); 111 | progress.report({ message: "binary downloaded" }); 112 | } catch (e) { 113 | vscode.window.showErrorMessage(`${e}`); 114 | } 115 | }, 116 | ); 117 | } 118 | rustowlCommand = await getRustowlCommand(dirPath); 119 | 120 | if (!rustowlCommand) { 121 | throw Error("failed to install RustOwl"); 122 | } 123 | await vscode.window.withProgress( 124 | { 125 | location: vscode.ProgressLocation.Notification, 126 | title: "Setup RustOwl toolchain", 127 | cancellable: false, 128 | }, 129 | async (progress) => { 130 | try { 131 | const installer = spawn(rustowlCommand, ["toolchain", "install"], { 132 | stdio: ["ignore", "ignore", "pipe"], 133 | }); 134 | installer.stderr.addListener("data", (data) => { 135 | if (`${data}`.includes("%")) { 136 | progress.report({ message: "toolchain downloading", increment: 1 }); 137 | } 138 | }); 139 | return new Promise((resolve, reject) => { 140 | installer.addListener("exit", (code) => { 141 | if (code === 0) { 142 | resolve(code); 143 | } else { 144 | reject(`toolchain setup failed (exit code ${code})`); 145 | } 146 | }); 147 | }); 148 | } catch (e) { 149 | vscode.window.showErrorMessage(`${e}`); 150 | } 151 | }, 152 | ); 153 | return rustowlCommand; 154 | }; 155 | -------------------------------------------------------------------------------- /vscode/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | 3 | import { zInfer, zLspCursorResponse, zLspRange } from "./schemas"; 4 | import { bootstrapRustowl } from "./bootstrap"; 5 | import { 6 | LanguageClient, 7 | ServerOptions, 8 | Executable, 9 | TransportKind, 10 | LanguageClientOptions, 11 | } from "vscode-languageclient/node"; 12 | 13 | let client: LanguageClient | undefined = undefined; 14 | 15 | let decoTimer: NodeJS.Timeout | null = null; 16 | 17 | export function activate(context: vscode.ExtensionContext) { 18 | console.log("rustowl activated"); 19 | 20 | const lspExec: Executable = { 21 | command: "rustowl", 22 | transport: TransportKind.stdio, 23 | }; 24 | const serverOptions: ServerOptions = lspExec; 25 | const clientOptions: LanguageClientOptions = { 26 | documentSelector: [{ scheme: "file", language: "rust" }], 27 | }; 28 | 29 | (async () => { 30 | try { 31 | const exec = await bootstrapRustowl(context.globalStorageUri.fsPath); 32 | serverOptions.command = exec; 33 | 34 | client = new LanguageClient( 35 | "rustowl", 36 | "RustOwl", 37 | serverOptions, 38 | clientOptions, 39 | ); 40 | client.start(); 41 | } catch (e) { 42 | vscode.window.showErrorMessage(`Failed to start RustOwl\n${e}`); 43 | } 44 | })(); 45 | 46 | let activeEditor: vscode.TextEditor | undefined = 47 | vscode.window.activeTextEditor; 48 | 49 | const statusBar = vscode.window.createStatusBarItem( 50 | vscode.StatusBarAlignment.Left, 51 | 0, 52 | ); 53 | statusBar.text = "RustOwl"; 54 | statusBar.show(); 55 | 56 | let lifetimeDecorationType = vscode.window.createTextEditorDecorationType({}); 57 | let moveDecorationType = vscode.window.createTextEditorDecorationType({}); 58 | let imBorrowDecorationType = vscode.window.createTextEditorDecorationType({}); 59 | let mBorrowDecorationType = vscode.window.createTextEditorDecorationType({}); 60 | let outLiveDecorationType = vscode.window.createTextEditorDecorationType({}); 61 | let emptyDecorationType = vscode.window.createTextEditorDecorationType({}); 62 | 63 | // update decoration 64 | const updateDecoration = ( 65 | editor: vscode.TextEditor, 66 | data: zInfer, 67 | ) => { 68 | const rangeToRange = (range: zInfer) => { 69 | return new vscode.Range( 70 | new vscode.Position(range.start.line, range.start.character), 71 | new vscode.Position(range.end.line, range.end.character), 72 | ); 73 | }; 74 | 75 | const { 76 | underlineThickness, 77 | lifetimeColor, 78 | moveCallColor, 79 | immutableBorrowColor, 80 | mutableBorrowColor, 81 | outliveColor, 82 | } = vscode.workspace.getConfiguration("rustowl"); 83 | 84 | lifetimeDecorationType = vscode.window.createTextEditorDecorationType({ 85 | textDecoration: `underline solid ${underlineThickness}px ${lifetimeColor}`, 86 | }); 87 | moveDecorationType = vscode.window.createTextEditorDecorationType({ 88 | textDecoration: `underline solid ${underlineThickness}px ${moveCallColor}`, 89 | }); 90 | imBorrowDecorationType = vscode.window.createTextEditorDecorationType({ 91 | textDecoration: `underline solid ${underlineThickness}px ${immutableBorrowColor}`, 92 | }); 93 | mBorrowDecorationType = vscode.window.createTextEditorDecorationType({ 94 | textDecoration: `underline solid ${underlineThickness}px ${mutableBorrowColor}`, 95 | }); 96 | outLiveDecorationType = vscode.window.createTextEditorDecorationType({ 97 | textDecoration: `underline solid ${underlineThickness}px ${outliveColor}`, 98 | }); 99 | emptyDecorationType = vscode.window.createTextEditorDecorationType({}); 100 | 101 | const lifetime: vscode.DecorationOptions[] = []; 102 | const immut: vscode.DecorationOptions[] = []; 103 | const mut: vscode.DecorationOptions[] = []; 104 | const moveCall: vscode.DecorationOptions[] = []; 105 | const outlive: vscode.DecorationOptions[] = []; 106 | const messages: vscode.DecorationOptions[] = []; 107 | for (const deco of data.decorations) { 108 | const range = rangeToRange(deco.range); 109 | if (!deco.overlapped) { 110 | if (deco.type === "lifetime") { 111 | lifetime.push({ 112 | range, 113 | }); 114 | } else if (deco.type === "imm_borrow") { 115 | immut.push({ range }); 116 | } else if (deco.type === "mut_borrow") { 117 | mut.push({ range }); 118 | } else if (deco.type === "call" || deco.type === "move") { 119 | moveCall.push({ range }); 120 | } else if (deco.type === "shared_mut" || deco.type === "outlive") { 121 | outlive.push({ range }); 122 | } 123 | } 124 | if ("hover_text" in deco && deco.hover_text) { 125 | messages.push({ range, hoverMessage: deco.hover_text }); 126 | } 127 | } 128 | editor.setDecorations(lifetimeDecorationType, lifetime); 129 | editor.setDecorations(imBorrowDecorationType, immut); 130 | editor.setDecorations(mBorrowDecorationType, mut); 131 | editor.setDecorations(moveDecorationType, moveCall); 132 | editor.setDecorations(outLiveDecorationType, outlive); 133 | editor.setDecorations(emptyDecorationType, messages); 134 | }; 135 | const resetDecoration = () => { 136 | lifetimeDecorationType.dispose(); 137 | moveDecorationType.dispose(); 138 | imBorrowDecorationType.dispose(); 139 | mBorrowDecorationType.dispose(); 140 | outLiveDecorationType.dispose(); 141 | emptyDecorationType.dispose(); 142 | }; 143 | 144 | const rustowlHoverRequest = async ( 145 | textEditor: vscode.TextEditor, 146 | select: vscode.Position, 147 | uri: vscode.Uri, 148 | ) => { 149 | const req = client?.sendRequest("rustowl/cursor", { 150 | position: { 151 | line: select.line, 152 | character: select.character, 153 | }, 154 | document: { uri: uri.toString() }, 155 | }); 156 | const resp = await req; 157 | const data = zLspCursorResponse.safeParse(resp); 158 | if (data.success) { 159 | console.log(data.data); 160 | if (data.data.status === "finished") { 161 | statusBar.text = "$(check) RustOwl"; 162 | statusBar.tooltip = "analyze finished"; 163 | } else if (data.data.status === "analyzing") { 164 | statusBar.text = "$(loading~spin) RustOwl"; 165 | statusBar.tooltip = "analyzing"; 166 | } else { 167 | statusBar.text = "$(error) RustOwl"; 168 | statusBar.tooltip = "analyze failed"; 169 | statusBar.command = { 170 | command: "rustowlHover", 171 | title: "Analyze", 172 | tooltip: "Rerun analysis", 173 | }; 174 | } 175 | statusBar.show(); 176 | updateDecoration(textEditor, data.data); 177 | } 178 | }; 179 | 180 | vscode.commands.registerCommand("rustowlHover", async (_args) => { 181 | if (activeEditor) { 182 | await rustowlHoverRequest( 183 | activeEditor, 184 | activeEditor?.selection.active, 185 | activeEditor?.document.uri, 186 | ); 187 | } 188 | }); 189 | 190 | // events 191 | vscode.window.onDidChangeActiveTextEditor( 192 | (editor) => { 193 | activeEditor = editor; 194 | }, 195 | null, 196 | context.subscriptions, 197 | ); 198 | //let timeout: NodeJS.Timeout | undefined = undefined; 199 | vscode.workspace.onDidSaveTextDocument( 200 | (_ev) => {}, 201 | null, 202 | context.subscriptions, 203 | ); 204 | vscode.window.onDidChangeTextEditorSelection( 205 | (ev) => { 206 | const { displayDelay } = vscode.workspace.getConfiguration("rustowl"); 207 | if (ev.textEditor === activeEditor) { 208 | resetDecoration(); 209 | if (decoTimer) { 210 | clearTimeout(decoTimer); 211 | decoTimer = null; 212 | } 213 | decoTimer = setTimeout(async () => { 214 | const select = ev.textEditor.selection.active; 215 | const uri = ev.textEditor.document.uri; 216 | rustowlHoverRequest(ev.textEditor, select, uri); 217 | }, displayDelay); 218 | } 219 | }, 220 | null, 221 | context.subscriptions, 222 | ); 223 | } 224 | 225 | export function deactivate() { 226 | if (client) { 227 | client.stop(); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /vscode/src/schemas.ts: -------------------------------------------------------------------------------- 1 | import { z, ZodSchema } from "zod"; 2 | 3 | export const zAliveMessage = z.object({ status: z.literal(true) }); 4 | 5 | export const zIndex = z.number().int(); 6 | export const zLoc = z.number().int(); 7 | export const zRange = z.object({ from: zLoc, until: zLoc }); 8 | export const zMirDecl = z.union([ 9 | z.object({ 10 | type: z.literal("user"), 11 | local_index: zIndex, 12 | name: z.string(), 13 | span: zRange, 14 | ty: z.string(), 15 | lives: zRange.array(), 16 | must_live_at: zRange.array(), 17 | drop: z.boolean(), 18 | drop_range: zRange.array(), 19 | //can_live_at: zRange.array(), 20 | }), 21 | z.object({ 22 | type: z.literal("other"), 23 | local_index: zIndex, 24 | ty: z.string(), 25 | lives: zRange.array(), 26 | must_live_at: zRange.array(), 27 | drop: z.boolean(), 28 | drop_range: zRange.array(), 29 | //can_live_at: zRange.array(), 30 | }), 31 | ]); 32 | export const zMirRval = z.union([ 33 | z.object({ 34 | type: z.literal("move"), 35 | target_local_index: zIndex, 36 | range: zRange, 37 | }), 38 | z.object({ 39 | type: z.literal("borrow"), 40 | target_local_index: zIndex, 41 | range: zRange, 42 | mutable: z.boolean(), 43 | outlive: zRange.nullish(), 44 | }), 45 | ]); 46 | export const zMirStatement = z.union([ 47 | z.object({ 48 | type: z.literal("storage_live"), 49 | target_local_index: zIndex, 50 | range: zRange, 51 | }), 52 | z.object({ 53 | type: z.literal("storage_dead"), 54 | target_local_index: zIndex, 55 | range: zRange, 56 | }), 57 | z.object({ 58 | type: z.literal("assign"), 59 | target_local_index: zIndex, 60 | range: zRange, 61 | rval: zMirRval.nullish(), 62 | }), 63 | ]); 64 | export const zMirTerminator = z.union([ 65 | z.object({ type: z.literal("drop"), local_index: zIndex, range: zRange }), 66 | z.object({ 67 | type: z.literal("call"), 68 | destination_local_index: zIndex, 69 | fn_span: zRange, 70 | }), 71 | z.object({ type: z.literal("other") }), 72 | ]); 73 | export const zMirBasicBlock = z.object({ 74 | statements: z.array(zMirStatement), 75 | terminator: zMirTerminator.nullish(), 76 | }); 77 | export const zMir = z.object({ 78 | basic_blocks: z.array(zMirBasicBlock), 79 | decls: z.array(zMirDecl), 80 | }); 81 | export const zItem = z.object({ 82 | type: z.literal("function"), 83 | span: zRange, 84 | mir: zMir, 85 | }); 86 | export const zCollectedData = z.object({ items: z.array(zMir) }); 87 | export const zWorkspace = z.record(zCollectedData); 88 | export const zAnalyzeSuccess = z.object({ 89 | success: z.literal(true), 90 | compile_error: z.boolean(), 91 | collected: zCollectedData, 92 | }); 93 | export const zAnalyzeResponse = z.union([ 94 | zAnalyzeSuccess, 95 | z.object({ success: z.literal(false), cause: z.string() }), 96 | ]); 97 | 98 | export type zInfer = z.infer; 99 | 100 | export const zLspLocation = z.object({ 101 | line: z.number().int(), 102 | character: z.number().int(), 103 | }); 104 | export const zLspRange = z.object({ start: zLspLocation, end: zLspLocation }); 105 | export const zLspType = z.union([ 106 | z.literal("lifetime"), 107 | z.literal("imm_borrow"), 108 | z.literal("mut_borrow"), 109 | z.literal("move"), 110 | z.literal("call"), 111 | z.literal("shared_mut"), 112 | z.literal("outlive"), 113 | ]); 114 | export const zLspCursorResponse = z.object({ 115 | is_analyzed: z.boolean(), 116 | status: z.union([ 117 | z.literal("analyzing"), 118 | z.literal("finished"), 119 | z.literal("error"), 120 | ]), 121 | decorations: z 122 | .object({ 123 | type: zLspType, 124 | range: zLspRange, 125 | hover_text: z.string().nullish(), 126 | overlapped: z.boolean(), 127 | }) 128 | .array(), 129 | }); 130 | -------------------------------------------------------------------------------- /vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "target": "ES2022", 5 | "lib": [ 6 | "ES2022" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | } 16 | } 17 | --------------------------------------------------------------------------------