Compare commits
340 Commits
feat/encoding
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c9e4c33948 | |||
| 15f2f19bfd | |||
| 0349ba04ca | |||
| b7ef6f9884 | |||
| 30cfb663aa | |||
| 80a0e0ddd0 | |||
| f8326d8b34 | |||
| bc83f0f89a | |||
| 4ae2437e74 | |||
| 8ce7c354e4 | |||
| b3017c17d9 | |||
| 2c17bd535f | |||
| 6cefd27acc | |||
| 28df4a3499 | |||
| ef488ca4a5 | |||
| b012cef466 | |||
| d92e8a8a78 | |||
| 59ce6df700 | |||
| a886d68d0b | |||
| 5aef5e6178 | |||
| 78871fe727 | |||
| 1d5ee125f4 | |||
| 707f94d4b7 | |||
| c426503c25 | |||
| fdfeebbf6a | |||
| 9cdbcf6bdf | |||
| dc3ea80094 | |||
| d36246f803 | |||
| 8f98d8eb97 | |||
| d61511609d | |||
| d979e6914e | |||
| 4fcd496511 | |||
| d41b4d52a8 | |||
| 9467bcea20 | |||
| 107a310063 | |||
| f543ba0d97 | |||
| 314e74fcfe | |||
| b52433e567 | |||
| ba703dd4aa | |||
| adaf8302b1 | |||
| 9531f60fd4 | |||
| 8bfcc8456d | |||
| f3994aa939 | |||
| a289ebd687 | |||
| 8d50fdadd1 | |||
| ce6d273f39 | |||
| eb83cee172 | |||
| 1a6bd6d571 | |||
| 4878ea3a97 | |||
| 24d01303ca | |||
| ae539020c9 | |||
| 5d93b62654 | |||
| 02a870c165 | |||
| 1d6ba03b08 | |||
| b411efbf46 | |||
| 115bc731f5 | |||
| c6959764c6 | |||
| f1f6a651de | |||
| bfdf7cb1c1 | |||
| e62abcc3e2 | |||
| 13d1cb4d87 | |||
| 795ea0d317 | |||
| 7100aa459e | |||
| 387a24b070 | |||
| a2aedb0cd3 | |||
| 6ef86351fc | |||
| ef22ae0c6d | |||
| 3a40e6b729 | |||
| 2b4fb8eb2c | |||
| 915a8eb6c1 | |||
| 9100b94167 | |||
| 2ac5de944c | |||
| c47bb53809 | |||
| 02ed2fe2cf | |||
| 10c17453c2 | |||
| bbe8b79569 | |||
| c826f94ec1 | |||
| 3c73763b2e | |||
| 2268ae14c4 | |||
| 8e12e0664f | |||
| f82d2f80e4 | |||
| 061c986c85 | |||
| 44a9bf7fb5 | |||
| 34c316e47a | |||
| cde488fb89 | |||
| dc9fa918d0 | |||
| 74729f8862 | |||
| f81d162c47 | |||
| a5b7accf61 | |||
| ca0e689a06 | |||
| db8ac2cdb6 | |||
| d75ce05ba9 | |||
| a99ce3e626 | |||
| ea9b7a9c9c | |||
| b6d11b39dc | |||
| b12d2b8a02 | |||
| d2627c1240 | |||
| a31277fd78 | |||
| d88fc896d7 | |||
| 4e7d353f44 | |||
| d5017ff859 | |||
| 7dc7d0707d | |||
| e1a409f77c | |||
| 44b546c419 | |||
| 18b1a70d02 | |||
| cfb7d204da | |||
| b0ea4481b1 | |||
| e69729efa1 | |||
| 0115151aa2 | |||
| 2773c5c9ff | |||
| a84ea98837 | |||
| ce154cedf4 | |||
| 2b8bc95a6e | |||
| 351cfa6cbc | |||
| eafaa138b1 | |||
| 513be4377d | |||
| b09ed0af01 | |||
| b8772caefc | |||
| 001d766a39 | |||
| a2869a4916 | |||
| 690339fe5e | |||
| debfbc4d22 | |||
| 29e7c3e1f0 | |||
| d3c87bfc1c | |||
| 0e55cc4ce8 | |||
| 88028d5af2 | |||
| fc021a6b19 | |||
| 6f629148ff | |||
| ad3c7e0bfc | |||
| f6ff3aac4e | |||
| dcc179e399 | |||
| ef65fa614a | |||
| 0403fb2bcc | |||
| 35c5ff451a | |||
| 3ff7ba9ca8 | |||
| a239243287 | |||
| 2815e17cc4 | |||
| 81333eb375 | |||
| 840b915c4b | |||
| 0adf2b3c96 | |||
| 3125034892 | |||
| 31178fdd8c | |||
| f690ea7dee | |||
| c88a8a0a78 | |||
| a983f0bddf | |||
| 5f07fc3334 | |||
| 76ed075cd9 | |||
| b618c25e1d | |||
| 8047100baf | |||
| 15958294e2 | |||
| 30ebe951d5 | |||
| 9ca4a99a9e | |||
| 1f22a05026 | |||
| 81f3838249 | |||
| 5f58268c4a | |||
| 74faab555f | |||
| e95283f51a | |||
| 3dd37e703b | |||
| b9062c01a7 | |||
| 06c5174555 | |||
| 8efe4bbdef | |||
| 0bbb901a3b | |||
| bb3709dfab | |||
| 082f63ec82 | |||
| 8d63ee769b | |||
| be4b2686cb | |||
| 48d09de958 | |||
| 08222a7d6f | |||
| e45e292bf0 | |||
| 7c3405c147 | |||
| 36bebb6d15 | |||
| cab5ddeac8 | |||
| ca40d8cb1e | |||
| c5c349380c | |||
| 8fc6fd59ae | |||
| 2639cc9f2d | |||
| 103206f50a | |||
| d9e0411763 | |||
| 8661de659d | |||
| 3a78eaa1ce | |||
| 69914369c6 | |||
| e00d5ca751 | |||
| 41a40a1a06 | |||
| 9dec920916 | |||
| 14f67a7e2a | |||
| 0f1aeddfc2 | |||
| a23f8aee7e | |||
| 16ebd02775 | |||
| 9c5e480ce8 | |||
| 87a517a1cd | |||
| 7a616d5553 | |||
| 41aaca3f5b | |||
| 98ebe57583 | |||
| 1d2372daa2 | |||
| 7d3830797b | |||
| fef57e8258 | |||
| 20fa316f2c | |||
| 8a610b0442 | |||
| ea999c653a | |||
| a6c21dc5c1 | |||
| bd2cfc510c | |||
| 930487449c | |||
| 2ed1f6c68b | |||
| 8f149d28f8 | |||
| b83823122c | |||
| ecea551209 | |||
| 86e1837232 | |||
| 94c83124ab | |||
| 80fed3987d | |||
| b40d8a68d4 | |||
| d9d7d1772e | |||
| 348bd17b46 | |||
| 6ffa4bb84b | |||
| 8236725896 | |||
| af42f186ed | |||
| e104f1db03 | |||
| 9f120f9016 | |||
| 0d0b08d0c8 | |||
| 07e7203962 | |||
| 7f1172246d | |||
| 11e4428c83 | |||
| 28302a43ec | |||
| db55cbe3bb | |||
| 0541f1cd8d | |||
| 67f8f82aaf | |||
| 3133098db3 | |||
| 51c7647d5a | |||
| 05ac2561ad | |||
| dd1dce96ef | |||
| ffdbb1faca | |||
| f5fddebb5d | |||
| f258bb782c | |||
| dc9a24d8e7 | |||
| 28988c8fa2 | |||
| caab22d091 | |||
| 73a1c79d12 | |||
| 45b5bad59e | |||
| 67696bd586 | |||
| d4c562fcae | |||
| 890438883e | |||
| e99621d971 | |||
| 35b60fd50f | |||
| 139ce92411 | |||
| 2768da1d88 | |||
| c6d39eca4b | |||
| a0316a1107 | |||
| 7c9099c4b3 | |||
| 6e5f1aa4fa | |||
| c4af55ee7c | |||
| fcc62ca7a1 | |||
| 901adddcc9 | |||
| f6e0e2023d | |||
| a9c1739ca4 | |||
| 4d4c098abc | |||
| 9b01cb192a | |||
| a28eca8787 | |||
| fd1794889a | |||
| e5212b90b4 | |||
| 45ecb8de84 | |||
| 527dc1d83a | |||
| 9008913c65 | |||
| 41b7ea4436 | |||
| 7d2aed5edb | |||
| faf8c9ba92 | |||
| 68243b57f5 | |||
| 6b5f008a88 | |||
| e17ff9c1ef | |||
| 965ad662f6 | |||
| bb28f43eed | |||
| 20d5f9f799 | |||
| a57dd5e0a7 | |||
| c0fc11ec96 | |||
| 8b1fb7a2ff | |||
| 02a4b1dc37 | |||
| 682ed93f23 | |||
| 4af4244397 | |||
| 44a54e2b39 | |||
| 6d7dc612ff | |||
| 32f3cd8f8c | |||
| bfb0358c53 | |||
| 90a6378e60 | |||
| f467fd8a81 | |||
| 552b57e67c | |||
| 28f9d75133 | |||
| 6f34384174 | |||
| b2bf83cce8 | |||
| a5e22c8af3 | |||
| 7abee828f0 | |||
| dade20f478 | |||
| 9a04401a47 | |||
| d3b3e5fb90 | |||
| f2355aa0b8 | |||
| 21192b6ec1 | |||
| f2ab022735 | |||
| 4bc49aa8db | |||
| 9ed68160e1 | |||
| 65a99d5945 | |||
| 3251d3d05c | |||
| 3ee00cebb8 | |||
| 2e601e50ab | |||
| af72732768 | |||
| 6a64a5331e | |||
| 9d016feb26 | |||
| 11f36a7038 | |||
| 0ddc216245 | |||
| 0ff10e346f | |||
| 5263087173 | |||
| 3f69ff4c18 | |||
| c604f513ae | |||
| c9f5f8fd20 | |||
| 985b07cca2 | |||
| 09065bb7af | |||
| 1004b7fbf3 | |||
| cbca98e108 | |||
| 75cfa069aa | |||
| 3dc59aeb27 | |||
| 43bdb124e3 | |||
| 6fd83c49bc | |||
| e9a87c0b01 | |||
| 8985acd81b | |||
| 0bc1c97575 | |||
| 36627a93ec | |||
| 42b219e642 | |||
| b066d164d3 | |||
| d390b5870c | |||
| f49ff95eee | |||
| 8531d7abe9 | |||
| 7c99eca99e | |||
| 49f4f50dfd | |||
| ab52dbcd57 | |||
| a234ae75e8 | |||
| abd6688fc5 | |||
| e2ec4234b7 | |||
| cc90d1e0bc | |||
| 7ebdf7f5bb | |||
| 468d416704 | |||
| 46f29719dd | |||
| d94668cea5 | |||
| 78b90a4ccd | |||
| 9fb61d0a72 |
@@ -0,0 +1,8 @@
|
||||
# Link runtime statically so Visual C++ Redist is not required
|
||||
[target.'cfg(all(windows, target_env = "msvc"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
opt-level = 0
|
||||
debug = false
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"image": "ghcr.io/lapce/lapdev-devcontainer-lapce",
|
||||
"forwardPorts": [6080],
|
||||
"portsAttributes": {
|
||||
"6080": {
|
||||
"label": "VNC",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -1 +1,9 @@
|
||||
/target
|
||||
.github
|
||||
.vscode
|
||||
.lapce
|
||||
docs
|
||||
extra/linux/docker
|
||||
extra/macos
|
||||
extra/windows
|
||||
target
|
||||
lapce.spec
|
||||
|
||||
+47
-7
@@ -1,12 +1,52 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
|
||||
- package-ecosystem: 'cargo'
|
||||
directory: '/'
|
||||
interval: "monthly"
|
||||
groups:
|
||||
all-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 10
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
interval: "monthly"
|
||||
groups:
|
||||
all-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
exclude-patterns:
|
||||
- "wasmtime*"
|
||||
- "wasi-common"
|
||||
- "wasi-experimental-http-wasmtime"
|
||||
- "tracing*"
|
||||
- "strum*"
|
||||
- "tree-sitter"
|
||||
- "floem*"
|
||||
wasmtime:
|
||||
patterns:
|
||||
- "wasmtime*"
|
||||
- "wasi-common"
|
||||
- "wasi-experimental-http-wasmtime"
|
||||
toml:
|
||||
patterns:
|
||||
- "toml"
|
||||
- "toml_edit"
|
||||
strum:
|
||||
patterns:
|
||||
- "strum"
|
||||
- "strum_macros"
|
||||
tracing:
|
||||
patterns:
|
||||
- "tracing*"
|
||||
floem:
|
||||
patterns:
|
||||
- "floem*"
|
||||
tree-sitter:
|
||||
patterns:
|
||||
- "tree-sitter"
|
||||
windows:
|
||||
patterns:
|
||||
- "windows*"
|
||||
|
||||
+40
-39
@@ -4,6 +4,12 @@ on:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- lapce-**
|
||||
- .github/workflows/ci.yml
|
||||
workflow_dispatch:
|
||||
|
||||
name: CI
|
||||
|
||||
@@ -15,71 +21,63 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Rust on ${{ matrix.os }} (${{ join(matrix.features, ',') }})
|
||||
name: Rust on ${{ matrix.os }}
|
||||
if: github.event.pull_request.draft == false
|
||||
needs: [fmt, clippy]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
include:
|
||||
- os: windows-latest
|
||||
features: []
|
||||
- os: windows-latest
|
||||
features:
|
||||
- lapce-app/portable
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Install dependencies on Ubuntu
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo make ubuntu-deps
|
||||
|
||||
- name: Update toolchain & add llvm-tools
|
||||
- name: Update toolchain
|
||||
run: |
|
||||
rustup update
|
||||
rustup component add llvm-tools-preview
|
||||
rustup update --no-self-update
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Build
|
||||
if: ${{ join(matrix.features, ',') == '' }}
|
||||
run: cargo build --frozen
|
||||
run: cargo build --profile ci --frozen
|
||||
|
||||
- name: Build
|
||||
if: ${{ join(matrix.features, ',') != '' }}
|
||||
run: cargo build --frozen --features ${{ join(matrix.features, ',') }}
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-${{ matrix.os }}
|
||||
path: |
|
||||
target/ci/lapce*
|
||||
retention-days: 1
|
||||
|
||||
- name: Free space
|
||||
- name: Build as portable
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: cargo build --profile ci --frozen --features lapce-app/portable
|
||||
|
||||
- name: Free space on Windows
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: cargo clean
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
|
||||
- name: Run tests and coverage
|
||||
if: ${{ toJSON(matrix.features) == '[]' }}
|
||||
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
|
||||
|
||||
- name: Run doc tests
|
||||
run: cargo test --doc --workspace
|
||||
|
||||
- uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: lcov.info
|
||||
run: cargo test --profile ci --doc --workspace
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
name: Formatting and spellchecking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Update toolchain & add rustfmt
|
||||
run: |
|
||||
@@ -89,19 +87,22 @@ jobs:
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
- name: Spell Check Repo
|
||||
uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d
|
||||
|
||||
clippy:
|
||||
name: Clippy on ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Update toolchain & add clippy
|
||||
run: |
|
||||
rustup update
|
||||
rustup update --no-self-update
|
||||
rustup component add clippy
|
||||
|
||||
- name: Install dependencies on Ubuntu
|
||||
@@ -109,10 +110,10 @@ jobs:
|
||||
run: sudo make ubuntu-deps
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy
|
||||
run: cargo clippy --profile ci
|
||||
|
||||
@@ -24,6 +24,8 @@ env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
tagname:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -53,16 +55,16 @@ jobs:
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Update rust
|
||||
run: rustup update
|
||||
run: rustup update --no-self-update
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: cargo fetch --locked
|
||||
@@ -95,7 +97,7 @@ jobs:
|
||||
Start-Sleep -Seconds 10
|
||||
$compressor.close()
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-windows
|
||||
path: |
|
||||
@@ -109,8 +111,10 @@ jobs:
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- name: Build deb packages
|
||||
run: |
|
||||
docker buildx create --driver=docker-container --use
|
||||
@@ -137,7 +141,7 @@ jobs:
|
||||
mv ./vendor-config.toml ./vendor/
|
||||
tar -zcf vendor.tar.gz ./vendor/
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-linux
|
||||
path: |
|
||||
@@ -150,6 +154,8 @@ jobs:
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -158,136 +164,111 @@ jobs:
|
||||
os-version: bookworm
|
||||
- os-name: debian
|
||||
os-version: bullseye
|
||||
- os-name: ubuntu
|
||||
os-version: focal
|
||||
- os-name: ubuntu
|
||||
os-version: jammy
|
||||
- os-name: ubuntu
|
||||
os-version: lunar
|
||||
os-version: noble
|
||||
- os-name: ubuntu
|
||||
os-version: mantic
|
||||
# - os-name: ubuntu
|
||||
# os-version: noble
|
||||
os-version: plucky
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Build deb packages
|
||||
run: |
|
||||
docker buildx create --driver=docker-container --use
|
||||
docker buildx bake --pull ${{ matrix.os-name }}-${{ matrix.os-version }}-package
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
path: |
|
||||
./target/linux_*/*
|
||||
retention-days: 1
|
||||
|
||||
linux-musl:
|
||||
name: Build lapce-proxy for ${{ matrix.platform }}
|
||||
rpm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
CROSS_SYSROOT: /mnt/alpine-${{ matrix.platform }}
|
||||
PACKAGES: >
|
||||
zlib-static freetype-static fontconfig-static
|
||||
libgit2-static libssh2-static openssl-libs-static
|
||||
libssl3 gtk+3.0-dev http-parser-dev rustup
|
||||
build-base openssl-dev git lld clang
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- triple: x86_64-unknown-linux-musl
|
||||
platform: x86_64
|
||||
- triple: aarch64-unknown-linux-musl
|
||||
platform: aarch64
|
||||
- os-name: fedora
|
||||
os-version: 42
|
||||
- os-name: fedora
|
||||
os-version: 43
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Set up Alpine Linux for ${{ matrix.platform }} (target arch)
|
||||
id: alpine-target
|
||||
uses: jirutka/setup-alpine@v1
|
||||
with:
|
||||
arch: ${{ matrix.platform }}
|
||||
branch: v3.18
|
||||
packages: ${{ env.PACKAGES }}
|
||||
shell-name: alpine-target.sh
|
||||
|
||||
- name: Set up Alpine Linux for x86_64 (build arch)
|
||||
uses: jirutka/setup-alpine@v1
|
||||
with:
|
||||
arch: x86_64
|
||||
branch: v3.18
|
||||
packages: ${{ env.PACKAGES }}
|
||||
volumes: ${{ steps.alpine-target.outputs.root-path }}:${{ env.CROSS_SYSROOT }}
|
||||
shell-name: alpine.sh
|
||||
|
||||
- name: Install Rust stable toolchain via rustup
|
||||
run: rustup-init --target ${{ matrix.triple }} --default-toolchain stable --profile minimal -y
|
||||
shell: alpine.sh {0}
|
||||
|
||||
- name: Build ${{ matrix.triple }}
|
||||
shell: alpine.sh {0}
|
||||
env:
|
||||
LIBZ_SYS_STATIC: 1
|
||||
LIBSSH2_STATIC: 1
|
||||
LIBGIT2_STATIC: 1
|
||||
OPENSSL_STATIC: 1
|
||||
OPENSSL_DIR: ${{ env.CROSS_SYSROOT }}/usr # static/dynamic lib workaround <3
|
||||
OPENSSL_NO_VENDOR: 1 # don't even try to build without it on musl
|
||||
PKG_CONFIG_ALL_STATIC: 1
|
||||
PKG_CONFIG_LIBDIR: ${{ env.CROSS_SYSROOT }}/usr/lib/pkgconfig
|
||||
RUSTFLAGS: -C target-feature=+crt-static -C linker=/usr/bin/ld.lld # link runtime static, use universal linker
|
||||
CARGO_BUILD_TARGET: ${{ matrix.triple }}
|
||||
CARGO_PROFILE_RELEASE_LTO: "true"
|
||||
CARGO_PROFILE_RELEASE_STRIP: symbols # remove unneeded debug stuff
|
||||
CARGO_PROFILE_RELEASE_OPT_LEVEL: "s" # optimise for size
|
||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "1" # optimise each crate
|
||||
SYSROOT: /dummy # workaround for https://github.com/rust-lang/pkg-config-rs/issues/102
|
||||
CC: clang
|
||||
- name: Build rpm packages
|
||||
run: |
|
||||
# Workaround for https://github.com/rust-lang/pkg-config-rs/issues/102
|
||||
echo -e '#!/bin/sh\nPKG_CONFIG_SYSROOT_DIR=${{ env.CROSS_SYSROOT }} exec pkgconf "$@"' \
|
||||
| install -m755 /dev/stdin pkg-config
|
||||
export PKG_CONFIG="$(pwd)/pkg-config"
|
||||
cargo fetch --locked
|
||||
cargo build \
|
||||
--frozen \
|
||||
--verbose \
|
||||
--release \
|
||||
--bin lapce-proxy \
|
||||
--manifest-path lapce-proxy/Cargo.toml
|
||||
docker buildx create --driver=docker-container --use
|
||||
docker buildx bake --pull ${{ matrix.os-name }}-${{ matrix.os-version }}-package
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
path: |
|
||||
./target/*
|
||||
retention-days: 1
|
||||
|
||||
lapce-proxy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os-name: alpine
|
||||
os-version: "" # uses latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a
|
||||
|
||||
- name: Build lapce-proxy binary
|
||||
run: |
|
||||
docker buildx create --driver=docker-container --use
|
||||
docker buildx bake --pull ${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
|
||||
- name: Gzip
|
||||
run: |
|
||||
gzip -c ./target/${{ matrix.triple }}/release/lapce-proxy > ./lapce-proxy-linux-${{ matrix.platform }}.gz
|
||||
gzip -c ./target/linux_amd64/lapce-proxy > ./lapce-proxy-linux-x86_64.gz
|
||||
gzip -c ./target/linux_arm64/lapce-proxy > ./lapce-proxy-linux-aarch64.gz
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-proxy-linux-${{ matrix.platform }}
|
||||
name: lapce-proxy-linux
|
||||
path: |
|
||||
./lapce-proxy-linux-*.gz
|
||||
retention-days: 1
|
||||
|
||||
macos:
|
||||
runs-on: macos-11
|
||||
runs-on: macos-15
|
||||
needs: tagname
|
||||
env:
|
||||
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
|
||||
|
||||
NOTARIZE_USERNAME: ${{ secrets.NOTARIZE_USERNAME }}
|
||||
NOTARIZE_PASSWORD: ${{ secrets.NOTARIZE_PASSWORD }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Install ARM target
|
||||
run: rustup update && rustup target add aarch64-apple-darwin
|
||||
run: rustup update && rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Import Certificate
|
||||
uses: lapce/import-codesign-certs@72dec84923586f8bef2bed09fdb4f9475c8f623d # use updated action, can be dropped once/if upstream is fixed
|
||||
uses: Apple-Actions/import-codesign-certs@b610f78488812c1e56b20e6df63ec42d833f2d14
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
p12-password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
@@ -308,33 +289,34 @@ jobs:
|
||||
gzip -c ./target/aarch64-apple-darwin/release-lto/lapce-proxy > ./target/release-lto/macos/lapce-proxy-darwin-aarch64.gz
|
||||
|
||||
- name: Notarize Release Build
|
||||
uses: lando/notarize-action@v2
|
||||
uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed
|
||||
with:
|
||||
product-path: "./target/release-lto/macos/Lapce-macos.dmg"
|
||||
appstore-connect-username: ${{ secrets.NOTARIZE_USERNAME }}
|
||||
appstore-connect-password: ${{ secrets.NOTARIZE_PASSWORD }}
|
||||
appstore-connect-team-id: CYSGAZFR8D
|
||||
primary-bundle-id: "io.lapce"
|
||||
|
||||
verbose: true
|
||||
|
||||
- name: "Staple Release Build"
|
||||
uses: lapce/xcode-staple@062485d6eeafe841c18a412f012e80f49e23c517
|
||||
with:
|
||||
product-path: "./target/release-lto/macos/Lapce-macos.dmg"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: lapce-macos
|
||||
path: |
|
||||
./target/release-lto/macos/lapce-proxy-darwin-*.gz
|
||||
./target/release-lto/macos/Lapce-macos.dmg
|
||||
retention-days: 1
|
||||
retention-days: 3
|
||||
|
||||
publish:
|
||||
needs:
|
||||
- linux
|
||||
- linux-musl
|
||||
- lapce-proxy
|
||||
- deb
|
||||
- rpm
|
||||
- windows
|
||||
- macos
|
||||
runs-on: ubuntu-latest
|
||||
@@ -346,9 +328,9 @@ jobs:
|
||||
steps:
|
||||
# Must perform checkout first, since it deletes the target directory
|
||||
# before running, and would therefore delete the downloaded artifacts
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
|
||||
- if: github.event_name == 'workflow_dispatch'
|
||||
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" | tee -a $GITHUB_ENV
|
||||
@@ -391,5 +373,6 @@ jobs:
|
||||
lapce-linux/* \
|
||||
lapce-debian*/*/* \
|
||||
lapce-ubuntu*/*/* \
|
||||
lapce-proxy-linux-*/* \
|
||||
lapce-fedora*/* \
|
||||
lapce-proxy-linux/* \
|
||||
lapce-windows/*
|
||||
|
||||
@@ -10,3 +10,5 @@ vendor/
|
||||
|
||||
.DS_Store
|
||||
*.patch
|
||||
*.tar
|
||||
*.gz
|
||||
|
||||
+121
-2
@@ -3,6 +3,112 @@
|
||||
## Unreleased
|
||||
|
||||
### Features/Changes
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
## 0.4.6
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- Add `.har` file extension as recognised JSON language (<https://github.com/lapce/lapce/pull/3853>)
|
||||
- Remove builds for Ubuntu Oracular, add Ubuntu Plucky (<https://github.com/lapce/lapce/pull/3808>)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix mouse wheel scrolling when viewing diff (<https://github.com/lapce/lapce/issues/3821>)
|
||||
- Fix editor tabs not selectable while appearing selectable (<https://github.com/lapce/lapce/pull/3818>)
|
||||
- Fix flickering when reordering editor tabs (<https://github.com/lapce/lapce/pull/3818>)
|
||||
- Fix diff viewer to scroll using mouse (<https://github.com/lapce/lapce/issues/3821>)
|
||||
- Fix updating window scale when it changes via settings tab (<https://github.com/lapce/lapce/issues/3832>)
|
||||
- Fix vendor tarball not being included in GitHub release
|
||||
- Fix cursor style inconsistencies (<https://github.com/lapce/lapce/pull/3819>)
|
||||
|
||||
## 0.4.5
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix missing/incorrect metadata for Linux packages using AppStream metainfo
|
||||
- Fix incorrect version in macOS/Windows package metadata
|
||||
- Fix rendering issues when using custom scale display (<https://github.com/lapce/lapce/issues/3795>)
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- Update Fedora releases to use 41/42
|
||||
- Update Alpine to latest (used for building portable lapce-proxy)
|
||||
- Remove Ubuntu Focal (20.04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix dropdown menu disappearing after releasing mouse button (<https://github.com/lapce/lapce/issues/3786>)
|
||||
- Fix issue with cargo vendoring due to duplicate `dpi` crate
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- Add Ubuntu 24.04 LTS (noble) build
|
||||
- Use Diff syntax for files with .diff/.patch extensions by default.
|
||||
- Update titles of file dialogs for better clarity
|
||||
- Bump to Rust 2024 Edition
|
||||
- Implement "Find References" panel
|
||||
- Implement "Go To Implementation" panel
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix pointer event not working on plugin panel
|
||||
- Fix rename/new file editor in file explorer is too small
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- Implement "Run in terminal"
|
||||
- Implement document symbols in a panel
|
||||
- Implement "Go To Location" functionality in the Diff editor.
|
||||
- Implement on screen find which is similar to `f` in vim but for the whole screen.
|
||||
- Make file explorer horizontal scrollable
|
||||
- Implement "Reveal in system file explorer"
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix markdown syntax highlighting
|
||||
- Fix click issue on window error message
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- Add fedora builds
|
||||
- Finish tree sitter dynamic library support by downloading from https://github.com/lapce/tree-sitter-grammars
|
||||
- Saves scale configuration in settings to restore at startup
|
||||
- text in ui can be selected and copied
|
||||
- add right click context menu for editor tab
|
||||
- terminal: added support for Alt+ some character input
|
||||
- Terminal: right-click to add "clear all" function
|
||||
- terminal: double-click to maximize bottom
|
||||
- initial lsp code lens support for rust-analyzer
|
||||
- Context menu for "reveal file in explorer"
|
||||
- support rust-analyzer's notification "experimental/serverStatus"
|
||||
- jump to definition support in Inlay Hints
|
||||
- "Show Call Hierarchy" support
|
||||
- Add proxy support and retry to all HTTPS GET requests.
|
||||
- Implement the drag-and-drop to open file/folder
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#3203](https://github.com/lapce/lapce/pull/3203): Fallback to default theme is theme key is not found
|
||||
- use windows subsystem to prevent console running
|
||||
- reimplement gotodefinition on cmd/ctrl+click
|
||||
- fix crash on tinyskia renderer
|
||||
- show file status for scratch documents
|
||||
- fix crash due to locale_config
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- [#2723](https://github.com/lapce/lapce/pull/2723): Line wrapping based on width (no column-based yet)
|
||||
- [#1277](https://github.com/lapce/lapce/pull/1277): Error message prompted on missing git user.email and/or user.name
|
||||
- [#2910](https://github.com/lapce/lapce/pull/2910): Files can be compared in the diff editor
|
||||
@@ -15,24 +121,30 @@
|
||||
- [#3086](https://github.com/lapce/lapce/pull/3086): Add folding to panels
|
||||
- [#3095](https://github.com/lapce/lapce/pull/3095): Add option to hide the Open Editors section in the explorer
|
||||
- [#3096](https://github.com/lapce/lapce/pull/3096): Add source control status coloring to file explorer
|
||||
- [#2989](https://github.com/lapce/lapce/pull/2989): Add building packages for linux distros
|
||||
- [#3123](https://github.com/lapce/lapce/pull/3123): Swap sled to plain text file for db
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#2779](https://github.com/lapce/lapce/pull/2779): Fix files detection on fresh git/VCS repository
|
||||
- [#3031](https://github.com/lapce/lapce/pull/3031): Fix find not receiving inputs when clicked
|
||||
- [#3142](https://github.com/lapce/lapce/pull/3142): Fix terminal default profile saving incorrectly
|
||||
- [#3158](https://github.com/lapce/lapce/pull/3158): Fix ShowHover command for showing hover with keybind
|
||||
- [#3119](https://github.com/lapce/lapce/pull/3119): Fix non US keyboard layout
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Features/Changes
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#2754](https://github.com/lapce/lapce/pull/2754): Don't mark nonexistent files as read only (fix saving new files)
|
||||
- [#2819](https://github.com/lapce/lapce/issues/2819): `Save Without Formatting` doesn't save the file
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- [#2190](https://github.com/lapce/lapce/pull/2190): Rewrite with Floem UI
|
||||
- [#2425](https://github.com/lapce/lapce/pull/2425): Reimplement completion lens
|
||||
- [#2498](https://github.com/lapce/lapce/pull/2498): Show Lapce as an option when doing "Open With..." on Linux
|
||||
@@ -58,6 +170,7 @@
|
||||
### Features/Changes
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#2209](https://github.com/lapce/lapce/pull/2209): Fix macOS crashes
|
||||
- [#2228](https://github.com/lapce/lapce/pull/2228): Fix `.desktop` entry to properly associate with Lapce on Wayland
|
||||
|
||||
@@ -69,6 +182,7 @@
|
||||
- [#1988](https://github.com/lapce/lapce/pull/1987): Replace modal status background with background/foreground theme keys
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- [#1899](https://github.com/lapce/lapce/pull/1899): Improve sorting files with numbers
|
||||
- [#1831](https://github.com/lapce/lapce/pull/1831): Plugin settings shown on right click
|
||||
- [#1830](https://github.com/lapce/lapce/pull/1830): Adds Clojure language support
|
||||
@@ -92,6 +206,7 @@
|
||||
- 
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#1911](https://github.com/lapce/lapce/pull/1911): Fix movement on selections with left/right arrow keys
|
||||
- [#1939](https://github.com/lapce/lapce/pull/1939): Fix saving/editing newly saved-as files
|
||||
- [#1971](https://github.com/lapce/lapce/pull/1971): Fix up/down movement on first/last line
|
||||
@@ -109,6 +224,7 @@
|
||||
- [#1726](https://github.com/lapce/lapce/pull/1726): Add more panel theme keys, apply hover first, then current item colour
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- [#1791](https://github.com/lapce/lapce/pull/1791): Add highlighting for scope lines
|
||||
- [#1767](https://github.com/lapce/lapce/pull/1767): Added CMake tree-sitter syntax highlighting
|
||||
- [#1759](https://github.com/lapce/lapce/pull/1759): Update C tree-sitter and highlight queries
|
||||
@@ -118,11 +234,12 @@
|
||||
- [#1723](https://github.com/lapce/lapce/pull/1723): In the palette, display the keybind for a command adjacent to it
|
||||
- [#1722](https://github.com/lapce/lapce/pull/1722): Add 'Save without Formatting'; Add option to disable formatting on autosave
|
||||
- [#1741](https://github.com/lapce/lapce/pull/1741): Add syntax highlighting for glsl
|
||||
- [#1756](https://github.com/lapce/lapce/pull/1756): Add support for ssh port with ```[user@]host[:port]```
|
||||
- [#1756](https://github.com/lapce/lapce/pull/1756): Add support for ssh port with `[user@]host[:port]`
|
||||
- [#1760](https://github.com/lapce/lapce/pull/1760): Add vim motions `cw`, `ce`, `cc`, `S`, and QOL modal bind `gf`
|
||||
- [#1770](https://github.com/lapce/lapce/pull/1770): Add support for terminal tabs
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#1771](https://github.com/lapce/lapce/pull/1771): Update tree-sitter-bash
|
||||
- [#1737](https://github.com/lapce/lapce/pull/1726): Fix an issue that plugins can't be upgraded
|
||||
- [#1724](https://github.com/lapce/lapce/pull/1724): Files and hidden folders no longer will be considered when trying to open a plugin base folder
|
||||
@@ -132,6 +249,7 @@
|
||||
## 0.2.4
|
||||
|
||||
### Features/Changes
|
||||
|
||||
- [#1700](https://github.com/lapce/lapce/pull/1700): Add prisma syntax and highlighting
|
||||
- [#1702](https://github.com/lapce/lapce/pull/1702): Improved svelte treesitter queries
|
||||
- [#1690](https://github.com/lapce/lapce/pull/1690): Add codelens and sticky headers for Dart
|
||||
@@ -139,6 +257,7 @@
|
||||
- [#1715](https://github.com/lapce/lapce/pull/1715): Add support for requests from plugins
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#1710](https://github.com/lapce/lapce/pull/1710): Fix autosave trying to save scratch files
|
||||
- [#1709](https://github.com/lapce/lapce/pull/1709): Fix search result ordering
|
||||
- [#1708](https://github.com/lapce/lapce/pull/1708): Fix visual issue when search panel is placed to either side panel
|
||||
@@ -199,7 +318,7 @@
|
||||
- [#1459](https://github.com/lapce/lapce/pull/1459): Fix opening currently used logfile
|
||||
- [#1505](https://github.com/lapce/lapce/pull/1505): Fix proxy download for hosts with curl without -Z flag
|
||||
- [#1483](https://github.com/lapce/lapce/pull/1483): Fix showing the close icon for the first tab when opening multiple tab
|
||||
- [#1477](https://github.com/lapce/lapce/pull/1477): Now use `esc` to close searchbar regarless of the current focus
|
||||
- [#1477](https://github.com/lapce/lapce/pull/1477): Now use `esc` to close searchbar regardless of the current focus
|
||||
- [#1507](https://github.com/lapce/lapce/pull/1507): Fixed a crash when scratch buffer is closed
|
||||
- [#1547](https://github.com/lapce/lapce/pull/1547): Fix infinite cycle in workspace symbol search
|
||||
- [#1628](https://github.com/lapce/lapce/pull/1541): Fix kts files not being recognized
|
||||
|
||||
Generated
+2037
-1399
File diff suppressed because it is too large
Load Diff
+33
-15
@@ -23,9 +23,9 @@ path = "lapce-proxy/src/bin/lapce-proxy.rs"
|
||||
members = ["lapce-app", "lapce-proxy", "lapce-rpc", "lapce-core"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.75"
|
||||
version = "0.4.6"
|
||||
edition = "2024"
|
||||
rust-version = "1.87.0"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://lapce.dev"
|
||||
authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
|
||||
@@ -38,29 +38,34 @@ clap = { version = "4.5.0", default-features = false, features = ["
|
||||
crossbeam-channel = { version = "0.5.12" }
|
||||
directories = { version = "4.0.1" }
|
||||
flate2 = { version = "1.0" }
|
||||
git2 = { version = "0.20.0", features = ["vendored-openssl"] }
|
||||
globset = { version = "0.4.14" }
|
||||
hashbrown = { version = "0.14.3", features = ["serde"] }
|
||||
hashbrown = { version = "0.14.5", features = ["serde"] }
|
||||
im = { version = "15.0.0", features = ["serde"] }
|
||||
include_dir = { version = "0.6.2" }
|
||||
include_dir = { version = "0.7" }
|
||||
indexmap = { version = "2.0", features = ["serde"] }
|
||||
interprocess = { version = "1.2.1" }
|
||||
itertools = { version = "0.12.1" }
|
||||
notify = { version = "5.2.0", features = ["serde"] }
|
||||
once_cell = { version = "1.19" }
|
||||
parking_lot = { version = "0.12.0" }
|
||||
parking_lot = { version = "0.12.3" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.4" }
|
||||
regex = { version = "1.10.5" }
|
||||
remain = { version = "0.2" }
|
||||
semver = { version = "1.0" }
|
||||
reqwest = { version = "0.11", features = ["blocking", "json", "socks"] }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = { version = "1.0" }
|
||||
smallvec = { version = "1.13.1" }
|
||||
strum = { version = "0.26.2" }
|
||||
strum_macros = { version = "0.26.2" }
|
||||
smallvec = { version = "1.15.1" }
|
||||
strum = { version = "0.27.1" }
|
||||
strum_macros = { version = "0.27.1" }
|
||||
tar = { version = "0.4" }
|
||||
tempfile = { version = "3.10.1" }
|
||||
thiserror = { version = "1.0" }
|
||||
toml = { version = "*" }
|
||||
toml_edit = { version = "0.20.2", features = ["serde"] }
|
||||
url = { version = "2.5.0" }
|
||||
zstd = { version = "0.11.2" } # follow same version wasmtime-cache in lockfile
|
||||
|
||||
lsp-types = { version = "0.95.1", features = ["proposed"] } # not following semver, so should be locked to patch version updates only
|
||||
psp-types = { git = "https://github.com/lapce/psp-types", rev = "f7fea28f59e7b2d6faa1034a21679ad49b3524ad" }
|
||||
@@ -71,14 +76,27 @@ lapce-core = { path = "./lapce-core" }
|
||||
lapce-rpc = { path = "./lapce-rpc" }
|
||||
lapce-proxy = { path = "./lapce-proxy" }
|
||||
|
||||
floem-editor-core = { git = "https://github.com/lapce/floem", rev = "1477931896d14a9dafbfc8159e7a52a9d8e1d8b8", features = ["serde"] }
|
||||
# floem-editor-core = { path = "../workspaces/floem/editor-core/", features = ["serde"] }
|
||||
[workspace.dependencies.floem]
|
||||
# path = "../floem"
|
||||
git = "https://github.com/lapce/floem"
|
||||
rev = "31fa8f444c37f4c314f47d88c23ffdbc25f2ab53"
|
||||
features = ["editor", "serde", "default-image-formats", "rfd-async-std"]
|
||||
|
||||
[workspace.dependencies.floem-editor-core]
|
||||
# path = "../floem/editor-core/"
|
||||
git = "https://github.com/lapce/floem"
|
||||
rev = "31fa8f444c37f4c314f47d88c23ffdbc25f2ab53"
|
||||
features = ["serde"]
|
||||
|
||||
[patch.crates-io]
|
||||
# Temporarily patch lsp-types with a version that supports inline-completion
|
||||
# Temporarily patch lsp-types with a version that adds message-type debug
|
||||
lsp-types = { git = "https://github.com/lapce/lsp-types", rev = "feaa1e2ec80975c9dadd400a238ceacf071058e6" }
|
||||
regalloc2 = { rev = "5d79e12d0a93b10fc181f4da409b4671dd365228", git = "https://github.com/bytecodealliance/regalloc2" }
|
||||
|
||||
# cargo vendor issue: https://github.com/rust-lang/cargo/issues/10310
|
||||
# dpi comes from winit (source) and muda (crate)
|
||||
dpi = { git = "https://github.com/rust-windowing/winit", rev = "ee245c569d65fdeacf705ee5eedb564508d10ebe" }
|
||||
|
||||
|
||||
[workspace.dependencies.tracing]
|
||||
git = "https://github.com/tokio-rs/tracing"
|
||||
@@ -101,7 +119,8 @@ rev = "908cc432a5994f6e17c8f36e13c217dc40085704"
|
||||
package = "tracing-appender"
|
||||
|
||||
[workspace.dependencies.alacritty_terminal]
|
||||
version = "0.23.0"
|
||||
git = "https://github.com/alacritty/alacritty"
|
||||
rev = "cacdb5bb3b72bad2c729227537979d95af75978f"
|
||||
|
||||
[workspace.dependencies.windows-sys]
|
||||
version = "0"
|
||||
@@ -125,4 +144,3 @@ opt-level = 3
|
||||
|
||||
[profile.fastdev]
|
||||
inherits = "dev"
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# This Makefile is intended *only* for building macOS binaries of Lapce.
|
||||
# It uses macOS-specific tools like `lipo`, `codesign`, and `hdiutil`,
|
||||
# and requires that a valid Apple Developer signing identity is installed
|
||||
# and available in the system Keychain under the fingerprint set in
|
||||
# CODESIGN_IDENTITY.
|
||||
#
|
||||
# See `docs/building-from-source.md`.
|
||||
|
||||
TARGET = lapce
|
||||
|
||||
CODESIGN_IDENTITY = FAC8FBEA99169DC1980731029648F110628D6A32
|
||||
@@ -22,7 +30,7 @@ vpath $(DMG_NAME) $(APP_DIR)
|
||||
all: help
|
||||
|
||||
help: ## Print this help message
|
||||
@grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
@grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
ubuntu-deps:
|
||||
apt-get update -y
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<br/>
|
||||
|
||||
|
||||
Lapce (IPA: /læps/) is written in pure Rust with a UI in [Floem](https://github.com/lapce/floem). It is designed with [Rope Science](https://xi-editor.io/docs/rope_science_00.html) from the [Xi-Editor](https://github.com/xi-editor/xi-editor) which makes for lightning-fast computation, and leverages [Wgpu](https://github.com/gfx-rs/wgpu) for rendering. More information about the features of Lapce can be found on the [main website](https://lapce.dev) and user documentation can be found on [GitBook](https://docs.lapce.dev/).
|
||||
Lapce (IPA: /læps/) is written in pure Rust, with a UI in [Floem](https://github.com/lapce/floem). It is designed with [Rope Science](https://xi-editor.io/docs/rope_science_00.html) from the [Xi-Editor](https://github.com/xi-editor/xi-editor), enabling lightning-fast computation, and leverages [wgpu](https://github.com/gfx-rs/wgpu) for rendering. More information about the features of Lapce can be found on the [main website](https://lapce.dev) and user documentation can be found on [GitBook](https://docs.lapce.dev/).
|
||||
|
||||

|
||||
|
||||
@@ -29,7 +29,7 @@ Lapce (IPA: /læps/) is written in pure Rust with a UI in [Floem](https://github
|
||||
|
||||
* Built-in LSP ([Language Server Protocol](https://microsoft.github.io/language-server-protocol/)) support to give you intelligent code features such as: completion, diagnostics and code actions
|
||||
* Modal editing support as first class citizen (Vim-like, and toggleable)
|
||||
* Built-in remote development support inspired by [VSCode Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Enjoy the benefits of a "local" experience, and seamlessly gain the full power of a remote system.
|
||||
* Built-in remote development support inspired by [VSCode Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Enjoy the benefits of a "local" experience, and seamlessly gain the full power of a remote system. We also have [Lapdev](https://lap.dev/) which can help manage your remote dev environments.
|
||||
* Plugins can be written in programming languages that can compile to the [WASI](https://wasi.dev/) format (C, Rust, [AssemblyScript](https://www.assemblyscript.org/))
|
||||
* Built-in terminal, so you can execute commands in your workspace, without leaving Lapce.
|
||||
|
||||
@@ -40,6 +40,12 @@ If you'd like to compile from source, you can find the [guide](docs/building-fro
|
||||
|
||||
## Contributing
|
||||
|
||||
<a href="https://ws.lap.dev/#https://github.com/lapce/lapce" target="_blank">
|
||||
<img src="https://lap.dev/images/open-in-lapdev.svg?version=8" alt="Open in Lapdev">
|
||||
</a>
|
||||
|
||||
[Lapdev](https://lap.dev/), developed by the Lapce team, is a cloud dev env service similar to GitHub Codespaces. By clicking the button above, you'll be taken to a fully set up Lapce dev env where you can browse the code and start developing. All dependencies are pre-installed, so you can get straight to code.
|
||||
|
||||
Guidelines for contributing to Lapce can be found in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
## Feedback & Contact
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# https://github.com/crate-ci/typos/blob/02ea592e44b3a53c302f697cddca7641cd051c3d/docs/reference.md
|
||||
|
||||
[files]
|
||||
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
# Ignore lines that end with `# spellchecker:disable-line`
|
||||
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",
|
||||
# Ignore the line after `# spellchecker:ignore-next-line`:
|
||||
"(#|//)\\s*spellchecker:ignore-next-line\\n.*",
|
||||
# Ignore blocks between `# spellchecker:off` and `# spellchecker:on`
|
||||
"(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on",
|
||||
]
|
||||
|
||||
[default.extend-identifiers]
|
||||
|
||||
[default.extend-words]
|
||||
@@ -68,6 +68,14 @@ dim-text = "#5C6370"
|
||||
"variable.other.member" = "$red"
|
||||
"tag" = "$blue"
|
||||
|
||||
"markup.heading" = "$red"
|
||||
"markup.bold" = "$orange"
|
||||
"markup.italic" = "$orange"
|
||||
"markup.list" = "$orange"
|
||||
"markup.link.url" = "$blue"
|
||||
"markup.link.label" = "$purple"
|
||||
"markup.link.text" = "$purple"
|
||||
|
||||
"bracket.color.1" = "$blue"
|
||||
"bracket.color.2" = "$yellow"
|
||||
"bracket.color.3" = "$purple"
|
||||
@@ -138,7 +146,7 @@ dim-text = "#5C6370"
|
||||
"source_control.removed" = "#FF5266CC"
|
||||
"source_control.modified" = "#0184BCCC"
|
||||
|
||||
"tooltip.background" = "#4B4D51"
|
||||
"tooltip.background" = "$primary-background"
|
||||
"tooltip.foreground" = "$text"
|
||||
|
||||
"palette.background" = "$secondary-background"
|
||||
|
||||
@@ -36,6 +36,7 @@ name = "Lapce Codicons"
|
||||
"keyboard" = "keyboard.svg"
|
||||
"breadcrumb_separator" = "chevron-right.svg"
|
||||
"symbol_color" = "symbol-color.svg"
|
||||
"type_hierarchy" = "type-hierarchy.svg"
|
||||
|
||||
"window.close" = "chrome-close.svg"
|
||||
"window.restore" = "chrome-restore.svg"
|
||||
@@ -66,7 +67,7 @@ name = "Lapce Codicons"
|
||||
"dropdown.arrow" = "chevron-down.svg"
|
||||
|
||||
"panel.fold-down" = "chevron-down.svg"
|
||||
"panel.fold-up" = "chevron-up.svg"
|
||||
"panel.fold-up" = "chevron-right.svg"
|
||||
|
||||
"location.forward" = "arrow-right.svg"
|
||||
"location.backward" = "arrow-left.svg"
|
||||
@@ -103,6 +104,9 @@ name = "Lapce Codicons"
|
||||
"search.replace" = "replace.svg"
|
||||
"search.replace_all" = "replace-all.svg"
|
||||
|
||||
"document_symbol" = "symbol-class.svg"
|
||||
"references" = "references.svg"
|
||||
"implementation" = "combine.svg"
|
||||
"symbol_kind.array" = "symbol-array.svg"
|
||||
"symbol_kind.boolean" = "symbol-boolean.svg"
|
||||
"symbol_kind.class" = "symbol-class.svg"
|
||||
|
||||
@@ -718,7 +718,7 @@ mode = "n"
|
||||
|
||||
[[keymaps]]
|
||||
key = "space"
|
||||
command = "toggle_code_lens"
|
||||
command = "toggle_code_glance"
|
||||
mode = "nv"
|
||||
|
||||
[[keymaps]]
|
||||
|
||||
@@ -16,7 +16,7 @@ command = "palette.command"
|
||||
|
||||
[[keymaps]]
|
||||
key = "meta+e"
|
||||
command = "toggle_code_lens"
|
||||
command = "toggle_code_glance"
|
||||
mode = "i"
|
||||
|
||||
[[keymaps]]
|
||||
@@ -292,11 +292,6 @@ command = "down"
|
||||
when = "!list_focus"
|
||||
mode = "i"
|
||||
|
||||
[[keymaps]]
|
||||
key = "right"
|
||||
command = "right"
|
||||
mode = "inv"
|
||||
|
||||
[[keymaps]]
|
||||
key = "meta+shift+o"
|
||||
command = "palette.symbol"
|
||||
|
||||
@@ -16,7 +16,7 @@ command = "palette.command"
|
||||
|
||||
[[keymaps]]
|
||||
key = "ctrl+e"
|
||||
command = "toggle_code_lens"
|
||||
command = "toggle_code_glance"
|
||||
mode = "i"
|
||||
|
||||
[[keymaps]]
|
||||
|
||||
@@ -74,6 +74,14 @@ dim-text = "#A0A1A7"
|
||||
"variable.other.member" = "$red"
|
||||
"tag" = "$blue"
|
||||
|
||||
"markup.heading" = "$red"
|
||||
"markup.bold" = "$orange"
|
||||
"markup.italic" = "$orange"
|
||||
"markup.list" = "$orange"
|
||||
"markup.link.url" = "$blue"
|
||||
"markup.link.label" = "$purple"
|
||||
"markup.link.text" = "$purple"
|
||||
|
||||
"bracket.color.1" = "$blue"
|
||||
"bracket.color.2" = "$yellow"
|
||||
"bracket.color.3" = "$purple"
|
||||
@@ -164,7 +172,7 @@ dim-text = "#A0A1A7"
|
||||
"source_control.removed" = "#FF5266CC"
|
||||
"source_control.modified" = "#0184BCCC"
|
||||
|
||||
"tooltip.background" = "#D6D6D6"
|
||||
"tooltip.background" = "$primary-background"
|
||||
"tooltip.foreground" = "$text"
|
||||
|
||||
"palette.background" = "$secondary-background"
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
|
||||
# The run config is used for both run mode and debug mode
|
||||
|
||||
[[configs]]
|
||||
@@ -7,7 +8,7 @@ name = "task"
|
||||
# the type of the debugger. If not set, it can't be debugged but can still be run
|
||||
# type = "lldb"
|
||||
|
||||
# the program to run
|
||||
# the program to run, e.g. "${workspace}\\target\\debug\\check.exe"
|
||||
program = ""
|
||||
|
||||
# the program arguments, e.g. args = ["arg1", "arg2"], optional
|
||||
|
||||
+11
-4
@@ -5,11 +5,13 @@ modal = false
|
||||
color-theme = "Lapce Dark"
|
||||
icon-theme = "Lapce Codicons"
|
||||
custom-titlebar = true
|
||||
file-explorer-double-click = false
|
||||
auto-reload-plugin = false
|
||||
|
||||
[editor]
|
||||
font-family = "Monospace"
|
||||
font-family = "monospace"
|
||||
font-size = 13
|
||||
code-lens-font-size = 2
|
||||
code-glance-font-size = 2
|
||||
line-height = 1.5
|
||||
smart-tab = true
|
||||
tab-width = 4
|
||||
@@ -21,7 +23,9 @@ wrap-style = "editor-width"
|
||||
wrap-column = 80
|
||||
wrap-width = 600 # px
|
||||
sticky-header = true
|
||||
completion-width = 600
|
||||
completion-show-documentation = true
|
||||
completion-item-show-detail = false
|
||||
show-signature = true
|
||||
signature-label-code-block = true
|
||||
auto-closing-matching-pairs = true
|
||||
@@ -39,6 +43,7 @@ enable-inlay-hints = true
|
||||
inlay-hint-font-family = ""
|
||||
inlay-hint-font-size = 0
|
||||
enable-error-lens = true
|
||||
only-render-error-styling = true
|
||||
error-lens-end-of-line = true
|
||||
error-lens-font-family = ""
|
||||
error-lens-font-size = 0
|
||||
@@ -77,19 +82,21 @@ default = {}
|
||||
# [terminal.profiles.example]
|
||||
# command = "cargo"
|
||||
# arguments = ["run"]
|
||||
# environemnt = { "KEY" = "VALUE" }
|
||||
# environment = { "KEY" = "VALUE" }
|
||||
# workdir = "/home/user"
|
||||
|
||||
[ui]
|
||||
scale = +1.0
|
||||
font-family = ""
|
||||
font-size = 13
|
||||
icon-size = 0
|
||||
header-height = 36
|
||||
status-height = 25
|
||||
tab-min-width = 100
|
||||
tab-separator-height = "Content"
|
||||
scroll-width = 10
|
||||
drop-shadow-width = 0
|
||||
preview-editor-width = 500
|
||||
palette-width = 500
|
||||
hover-font-family = ""
|
||||
hover-font-size = 0
|
||||
trim-search-results-whitespace = true
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[licenses]
|
||||
# use SPDX short identifiers
|
||||
allow = [
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
]
|
||||
+33
-20
@@ -1,5 +1,5 @@
|
||||
variable "RUST_VERSION" {
|
||||
default = "1.76"
|
||||
default = "1"
|
||||
}
|
||||
|
||||
variable "XX_VERSION" {
|
||||
@@ -129,13 +129,12 @@ target "ubuntu" {
|
||||
matrix = {
|
||||
os_name = ["ubuntu"]
|
||||
build = [
|
||||
{ packages = null, platforms = null, type = "package", os_version = "bionic" }, # 18.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "focal" }, # 20.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "jammy" }, # 22.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "kinetic" }, # 22.10
|
||||
{ packages = null, platforms = null, type = "package", os_version = "lunar" }, # 23.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "mantic" }, # 23.10
|
||||
{ packages = null, platforms = null, type = "package", os_version = "noble" }, # 24.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "bionic" }, # 18.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "focal" }, # 20.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "jammy" }, # 22.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "noble" }, # 24.04
|
||||
{ packages = null, platforms = null, type = "package", os_version = "oracular" }, # 24.10
|
||||
{ packages = null, platforms = null, type = "package", os_version = "plucky" }, # 25.04
|
||||
# static binary, it looks ugly to define the target this way
|
||||
# but I don't have a better way to make it more friendly on CLI side without
|
||||
# more terrible code-wise way to implement it
|
||||
@@ -160,24 +159,27 @@ variable "RHEL_FAMILY_PACKAGES" {
|
||||
}
|
||||
|
||||
target "fedora" {
|
||||
inherits = ["package"]
|
||||
name = "${os_name}-${build.os_version}"
|
||||
dockerfile = "extra/linux/docker/${os_name}/Dockerfile"
|
||||
inherits = [build.type]
|
||||
name = "${name}-${build.version}-${build.type}"
|
||||
dockerfile = "extra/linux/docker/${name}/Dockerfile"
|
||||
args = {
|
||||
XX_VERSION = "test"
|
||||
|
||||
DISTRIBUTION_NAME = os_name
|
||||
DISTRIBUTION_VERSION = build.os_version
|
||||
DISTRIBUTION_NAME = name
|
||||
DISTRIBUTION_VERSION = build.version
|
||||
DISTRIBUTION_PACKAGES = join(" ", coalesce(build.packages, RHEL_FAMILY_PACKAGES))
|
||||
}
|
||||
platforms = coalesce(build.platforms, platforms)
|
||||
// platforms = coalesce(build.platforms, platforms)
|
||||
platforms = ["linux/amd64"]
|
||||
matrix = {
|
||||
os_name = ["fedora"]
|
||||
name = ["fedora"]
|
||||
build = [
|
||||
{ os_version = "39", packages = null, platforms = null },
|
||||
{ os_version = "40", packages = null, platforms = null },
|
||||
{ os_version = "41", packages = null, platforms = null },
|
||||
{ os_version = "rawhide", packages = null, platforms = null },
|
||||
{ packages = null, platforms = null, type = "package", version = "39" },
|
||||
{ packages = null, platforms = null, type = "package", version = "40" },
|
||||
{ packages = null, platforms = null, type = "package", version = "41" },
|
||||
{ packages = null, platforms = null, type = "package", version = "42" },
|
||||
{ packages = null, platforms = null, type = "package", version = "43" },
|
||||
{ packages = null, platforms = null, type = "package", version = "rawhide" },
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -194,6 +196,7 @@ variable "APK_FAMILY_PACKAGES" {
|
||||
"lld",
|
||||
"build-base",
|
||||
"rustup",
|
||||
"openssl-dev",
|
||||
"openssl-libs-static",
|
||||
"libssh2-static",
|
||||
"libgit2-static",
|
||||
@@ -221,11 +224,21 @@ target "alpine" {
|
||||
matrix = {
|
||||
os_name = ["alpine"]
|
||||
build = [
|
||||
{ os_version = "", packages = null, platforms = null },
|
||||
{ os_version = "3.22", packages = null, platforms = null },
|
||||
{ os_version = "3.20", packages = null, platforms = null },
|
||||
{ os_version = "3.18", packages = null, platforms = null },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
target "cross-alpine" {
|
||||
inherits = ["alpine-3-18", "cross-binary"]
|
||||
inherits = ["alpine-3-20", "cross-binary"]
|
||||
}
|
||||
|
||||
target "alpine-dev" {
|
||||
inherits = ["alpine-3-20"]
|
||||
target = "dev"
|
||||
tags = ["lapce/lapce:dev"]
|
||||
output = ["type=docker"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
It is easy to build Lapce from source on a GNU/Linux distribution. Cargo handles the build process, all you need to do, is ensure the correct dependencies are installed.
|
||||
|
||||
1. Install the Rust compiler and Cargo using [`rustup.rs`](https://rustup.rs/). If you already have the toolchain, ensure you are using version 1.64 or higher.
|
||||
1. Install the Rust compiler and Cargo using [`rustup.rs`](https://rustup.rs/). If you already have the toolchain, ensure you are using latest Rust version.
|
||||
|
||||
2. Install dependencies for your operating system:
|
||||
|
||||
@@ -12,7 +12,7 @@ sudo apt install clang libxkbcommon-x11-dev pkg-config libvulkan-dev libwayland-
|
||||
```
|
||||
#### Fedora
|
||||
```sh
|
||||
sudo dnf install clang libxkbcommon-x11-devel libxcb-devel vulkan-loader-devel wayland-devel perl-File-Compare perl-FindBin
|
||||
sudo dnf install clang libxkbcommon-x11-devel libxcb-devel vulkan-loader-devel wayland-devel openssl-devel pkgconf
|
||||
```
|
||||
#### Void Linux
|
||||
```sh
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Updating versioning for package managers and whatnot
|
||||
|
||||
- App metainfo: `extra/linux/dev.lapce.lapce.metainfo.xml`
|
||||
- macOS plist (`CFBundleShortVersionString`): `extra/macos/Lapce.app/Contents/Info.plist`
|
||||
- Rust: `Cargo.toml`
|
||||
- Obviously changelog: `CHANGELOG.md`
|
||||
- RPM spec: `lapce.spec`
|
||||
- Windows wix spec (`<Product [...] Version=X.X.X>`): `extra/windows/wix/lapce.wxs`
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,187 @@
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
|
||||
Bitstream Vera Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute the
|
||||
Font Software, including without limitation the rights to use, copy, merge,
|
||||
publish, distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall
|
||||
be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional glyphs or characters may be added to the Fonts, only if the fonts
|
||||
are renamed to names not containing either the words "Bitstream" or the word
|
||||
"Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font
|
||||
Software that has been modified and is distributed under the "Bitstream
|
||||
Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no
|
||||
copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
|
||||
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
|
||||
FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
|
||||
Arev Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and
|
||||
associated documentation files (the "Font Software"), to reproduce
|
||||
and distribute the modifications to the Bitstream Vera Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute, and/or sell copies of the Font Software, and to permit
|
||||
persons to whom the Font Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Tavmjong Bah" or the word "Arev".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Tavmjong Bah Arev" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Tavmjong Bah shall not
|
||||
be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in this Font Software without prior written authorization
|
||||
from Tavmjong Bah. For further information, contact: tavmjong @ free
|
||||
. fr.
|
||||
|
||||
TeX Gyre DJV Math
|
||||
-----------------
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
|
||||
Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
|
||||
(on behalf of TeX users groups) are in public domain.
|
||||
|
||||
Letters imported from Euler Fraktur from AMSfonts are (c) American
|
||||
Mathematical Society (see below).
|
||||
Bitstream Vera Fonts Copyright
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
|
||||
is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license (“Fonts”) and associated
|
||||
documentation
|
||||
files (the “Font Software”), to reproduce and distribute the Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute,
|
||||
and/or sell copies of the Font Software, and to permit persons to whom
|
||||
the Font Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be
|
||||
included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional
|
||||
glyphs or characters may be added to the Fonts, only if the fonts are
|
||||
renamed
|
||||
to names not containing either the words “Bitstream” or the word “Vera”.
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or
|
||||
Font Software
|
||||
that has been modified and is distributed under the “Bitstream Vera”
|
||||
names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy
|
||||
of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
|
||||
SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
|
||||
ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
|
||||
INABILITY TO USE
|
||||
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Except as contained in this notice, the names of GNOME, the GNOME
|
||||
Foundation,
|
||||
and Bitstream Inc., shall not be used in advertising or otherwise to promote
|
||||
the sale, use or other dealings in this Font Software without prior written
|
||||
authorization from the GNOME Foundation or Bitstream Inc., respectively.
|
||||
For further information, contact: fonts at gnome dot org.
|
||||
|
||||
AMSFonts (v. 2.2) copyright
|
||||
|
||||
The PostScript Type 1 implementation of the AMSFonts produced by and
|
||||
previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
|
||||
available for general use. This has been accomplished through the
|
||||
cooperation
|
||||
of a consortium of scientific publishers with Blue Sky Research and Y&Y.
|
||||
Members of this consortium include:
|
||||
|
||||
Elsevier Science IBM Corporation Society for Industrial and Applied
|
||||
Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
|
||||
|
||||
In order to assure the authenticity of these fonts, copyright will be
|
||||
held by
|
||||
the American Mathematical Society. This is not meant to restrict in any way
|
||||
the legitimate use of the fonts, such as (but not limited to) electronic
|
||||
distribution of documents containing these fonts, inclusion of these fonts
|
||||
into other public domain or commercial font collections or computer
|
||||
applications, use of the outline data to create derivative fonts and/or
|
||||
faces, etc. However, the AMS does require that the AMS copyright notice be
|
||||
removed from any derivative versions of the fonts which have been altered in
|
||||
any way. In addition, to ensure the fidelity of TeX documents using Computer
|
||||
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
|
||||
has requested that any alterations which yield different font metrics be
|
||||
given a different name.
|
||||
|
||||
$Id$
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 944 KiB |
@@ -9,6 +9,10 @@
|
||||
<url type="homepage">https://lapce.dev/</url>
|
||||
<url type="bugtracker">https://github.com/lapce/lapce/issues</url>
|
||||
<url type="help">https://docs.lapce.dev/</url>
|
||||
<categories>
|
||||
<category>IDE</category>
|
||||
<category>Development</category>
|
||||
</categories>
|
||||
<description>
|
||||
<p>
|
||||
Lapce is an open source code editor written in Rust. By utilising native GUI and GPU rendering, and with the performance Rust provides, Lapce is one of the fastest code editors out there.
|
||||
@@ -25,11 +29,35 @@
|
||||
<content_rating type="oars-1.1" />
|
||||
<launchable type="desktop-id">dev.lapce.lapce.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/lapce/lapce/master/extra/images/screenshot.png</image>
|
||||
<screenshot type="default" environment="plasma">
|
||||
<caption>Lapce source project opened in Lapce with Rust source code file opened</caption>
|
||||
<image type="source" width="1000" height="700">https://raw.githubusercontent.com/lapce/lapce/refs/tags/v0.4.5/extra/images/linux/code-file-with-highlight.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="0.3.1" date="2023-12-04"/>
|
||||
<release version="0.4.6" date="2026-01-21">
|
||||
<url type="details">https://github.com/lapce/lapce/releases/tag/v0.4.6</url>
|
||||
<issues>
|
||||
<issue url="https://github.com/lapce/lapce/issues/3821">#3821</issue>
|
||||
<issue url="https://github.com/lapce/lapce/issues/3832">#3832</issue>
|
||||
<issue url="https://github.com/lapce/lapce/pull/3818">#3818</issue>
|
||||
<issue url="https://github.com/lapce/lapce/pull/3819">#3819</issue>
|
||||
</issues>
|
||||
</release>
|
||||
<release version="0.4.5" date="2025-09-05">
|
||||
<url type="details">https://github.com/lapce/lapce/releases/tag/v0.4.5</url>
|
||||
<issues>
|
||||
<issue url="https://github.com/lapce/lapce/issues/3795">#3795</issue>
|
||||
</issues>
|
||||
</release>
|
||||
<release version="0.4.4" date="2025-08-30">
|
||||
<url type="details">https://github.com/lapce/lapce/releases/tag/v0.4.4</url>
|
||||
<issues>
|
||||
<issue url="https://github.com/lapce/lapce/issues/3786">#3795</issue>
|
||||
</issues>
|
||||
</release>
|
||||
<release version="0.4.3" date="2024-06-27">
|
||||
<url type="details">https://github.com/lapce/lapce/releases/tag/v0.4.3</url>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -10,22 +10,22 @@ COPY --from=xx / /
|
||||
|
||||
SHELL [ "/bin/ash", "-c" ]
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# install host dependencies
|
||||
ARG DISTRIBUTION_PACKAGES
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=private \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=private \
|
||||
--mount=type=cache,target=/var/cache/apk,sharing=private \
|
||||
--mount=type=cache,target=/etc/apk/cache,sharing=private \
|
||||
<<EOF
|
||||
|
||||
apk upgrade -Ua
|
||||
apk add --no-cache \
|
||||
bash clang lld llvm file git sccache \
|
||||
cmake pkgconf build-base abuild \
|
||||
cmake pkgconf build-base abuild curl mold \
|
||||
${DISTRIBUTION_PACKAGES}
|
||||
EOF
|
||||
|
||||
SHELL [ "/bin/bash", "-c" ]
|
||||
|
||||
WORKDIR /source
|
||||
COPY --link . .
|
||||
|
||||
@@ -48,8 +48,8 @@ EOF
|
||||
ARG TARGETPLATFORM
|
||||
ARG DISTRIBUTION_PACKAGES
|
||||
RUN \
|
||||
--mount=type=cache,target=/var/cache/apt,sharing=private \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=private \
|
||||
--mount=type=cache,target=/var/cache/apk,sharing=private \
|
||||
--mount=type=cache,target=/etc/apk/cache,sharing=private \
|
||||
<<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
@@ -70,21 +70,9 @@ ENV OUTPUT_DIR="${OUTPUT_DIR}"
|
||||
ARG CARGO_BUILD_INCREMENTAL='false'
|
||||
ENV CARGO_TARGET_DIR='/root/.cache/rust'
|
||||
|
||||
ENV CC='xx-clang'
|
||||
ENV CXX='xx-clang++'
|
||||
|
||||
ENV OPENSSL_NO_VENDOR="1"
|
||||
ENV ZSTD_SYS_USE_PKG_CONFIG="1"
|
||||
|
||||
ARG RELEASE_TAG_NAME
|
||||
ENV RELEASE_TAG_NAME="${RELEASE_TAG_NAME}"
|
||||
|
||||
ENV LIBZ_SYS_STATIC="1"
|
||||
ENV LIBSSH2_STATIC="1"
|
||||
ENV LIBGIT2_STATIC="1"
|
||||
ENV OPENSSL_STATIC="1"
|
||||
ENV PKG_CONFIG_ALL_STATIC="1"
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/cargo/git/db,sharing=locked \
|
||||
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
|
||||
@@ -97,48 +85,39 @@ set -euxo pipefail
|
||||
xx-clang --setup-target-triple
|
||||
xx-clang --wrap
|
||||
|
||||
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
if ! xx-info is-cross; then
|
||||
export OPENSSL_DIR="/usr"
|
||||
else
|
||||
export OPENSSL_DIR="/$(xx-info triple)/usr"
|
||||
fi
|
||||
|
||||
export LIBZ_SYS_STATIC="1"
|
||||
export LIBSSH2_STATIC="1"
|
||||
export LIBGIT2_STATIC="1"
|
||||
export OPENSSL_STATIC="1"
|
||||
export PKG_CONFIG_ALL_STATIC="1"
|
||||
export OPENSSL_NO_VENDOR="1"
|
||||
export ZSTD_SYS_USE_PKG_CONFIG="1"
|
||||
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=mold -C target-feature=+crt-static"
|
||||
export PKG_CONFIG="$(xx-clang --print-prog-name=pkg-config)"
|
||||
export CARGO_BUILD_TARGET="$(xx-cargo --print-target-triple)"
|
||||
|
||||
xx-cargo build \
|
||||
--verbose \
|
||||
--frozen \
|
||||
--package lapce-app \
|
||||
--bin lapce-proxy \
|
||||
--profile release-lto \
|
||||
--no-default-features \
|
||||
--features all-languages
|
||||
--no-default-features
|
||||
|
||||
export RUSTFLAGS="${RUSTFLAGS} -C target-feature=+crt-static"
|
||||
|
||||
xx-cargo build
|
||||
|
||||
xx-verify "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce
|
||||
xx-verify "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce-proxy
|
||||
|
||||
mkdir -p /target
|
||||
mv -v "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce /target/
|
||||
|
||||
cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2 | tee /target/lapce.version
|
||||
mv -v "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce-proxy /usr/local/bin/
|
||||
EOF
|
||||
|
||||
WORKDIR /output
|
||||
RUN <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
|
||||
echo 'a'
|
||||
EOF
|
||||
|
||||
FROM build-base AS dev
|
||||
COPY . ./dev
|
||||
FROM build-prep AS dev
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build /output/lapce .
|
||||
COPY --from=build /usr/local/bin/lapce-proxy .
|
||||
|
||||
FROM scratch AS cross-binary
|
||||
COPY --from=build /output/lapce .
|
||||
|
||||
# FROM scratch AS package
|
||||
# COPY --from=build /output/*.deb .
|
||||
|
||||
# FROM scratch AS cross-package
|
||||
# COPY --from=build /output/*.deb .
|
||||
COPY --from=build /usr/local/bin/lapce-proxy .
|
||||
|
||||
@@ -101,8 +101,7 @@ xx-cargo build \
|
||||
--frozen \
|
||||
--package lapce-app \
|
||||
--profile release-lto \
|
||||
--no-default-features \
|
||||
--features all-languages
|
||||
--no-default-features
|
||||
|
||||
xx-verify "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce
|
||||
|
||||
|
||||
@@ -80,9 +80,6 @@ FROM build-prep AS build
|
||||
ARG PACKAGE_NAME
|
||||
ENV PACKAGE_NAME="${PACKAGE_NAME}"
|
||||
|
||||
ARG OUTPUT_DIR="/output"
|
||||
ENV OUTPUT_DIR="${OUTPUT_DIR}"
|
||||
|
||||
ARG CARGO_BUILD_INCREMENTAL='false'
|
||||
|
||||
ENV CC='xx-clang'
|
||||
@@ -95,58 +92,99 @@ ARG RELEASE_TAG_NAME
|
||||
ENV RELEASE_TAG_NAME="${RELEASE_TAG_NAME}"
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/cargo/git/db,sharing=locked,readonly=true \
|
||||
--mount=type=cache,target=/cargo/registry/cache,sharing=locked,readonly=true \
|
||||
--mount=type=cache,target=/cargo/registry/index,sharing=locked,readonly=true \
|
||||
--mount=type=cache,target=/cargo/git/db,readonly=true \
|
||||
--mount=type=cache,target=/cargo/registry/cache,readonly=true \
|
||||
--mount=type=cache,target=/cargo/registry/index,readonly=true \
|
||||
--mount=type=cache,target=/root/.cache,sharing=private \
|
||||
<<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
|
||||
LAPCE_VERSION=$(cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2)
|
||||
|
||||
case "${RELEASE_TAG_NAME}" in
|
||||
nightly-*)
|
||||
commit=$(echo "${RELEASE_TAG_NAME}" | cut -d'-' -f2)
|
||||
RELEASE_TAG_NAME="${LAPCE_VERSION}+${commit}"
|
||||
;;
|
||||
debug|nightly)
|
||||
date=$(date +%Y%m%d%H%M)
|
||||
RELEASE_TAG_NAME="${LAPCE_VERSION}+${date}"
|
||||
;;
|
||||
*)
|
||||
RELEASE_TAG_NAME="${RELEASE_TAG_NAME//v/}"
|
||||
;;
|
||||
esac
|
||||
|
||||
. /etc/os-release
|
||||
export DISTRO_ID="$(echo $ID | tr - _)"
|
||||
export DISTRO_VERSION="$(echo $VERSION_ID | tr - _)"
|
||||
|
||||
cat > lapce.spec <<EOL
|
||||
Name: ${PACKAGE_NAME}
|
||||
Version: ${RELEASE_TAG_NAME}
|
||||
Release: 1.${DISTRO_ID}${DISTRO_VERSION}
|
||||
Summary: Lightning-fast and Powerful Code Editor written in Rust
|
||||
License: Apache-2.0
|
||||
URL: https://github.com/lapce/lapce
|
||||
Packager: Jakub Panek
|
||||
|
||||
%description
|
||||
Lapce is written in pure Rust with a UI in Floem (which is also written in Rust).
|
||||
It is designed with Rope Science from the Xi-Editor which makes for lightning-fast computation, and leverages OpenGL for rendering.
|
||||
|
||||
%build
|
||||
xx-clang --setup-target-triple
|
||||
xx-clang --wrap
|
||||
|
||||
export RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=/usr/bin/ld.lld"
|
||||
export PKG_CONFIG="$(xx-clang --print-prog-name=pkg-config)"
|
||||
export CARGO_BUILD_TARGET="$(xx-cargo --print-target-triple)"
|
||||
|
||||
rustup target add "$(xx-cargo --print-target-triple)"
|
||||
export RELEASE_TAG_NAME="${RELEASE_TAG_NAME}"
|
||||
xx-cargo build --profile release-lto --bin lapce --frozen
|
||||
xx-verify "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce
|
||||
|
||||
mkdir -p /target
|
||||
mv -v "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce /target/
|
||||
%install
|
||||
install -Dm755 "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce %{buildroot}%{_bindir}/lapce
|
||||
install -Dm644 extra/linux/dev.lapce.lapce.desktop %{buildroot}/usr/share/applications/dev.lapce.lapce.desktop
|
||||
install -Dm644 extra/linux/dev.lapce.lapce.metainfo.xml %{buildroot}/usr/share/metainfo/dev.lapce.lapce.metainfo.xml
|
||||
install -Dm644 extra/images/logo.png %{buildroot}/usr/share/pixmaps/dev.lapce.lapce.png
|
||||
|
||||
%files
|
||||
%license LICENSE*
|
||||
%doc *.md
|
||||
%{_bindir}/lapce
|
||||
/usr/share/applications/dev.lapce.lapce.desktop
|
||||
/usr/share/metainfo/dev.lapce.lapce.metainfo.xml
|
||||
/usr/share/pixmaps/dev.lapce.lapce.png
|
||||
|
||||
%changelog
|
||||
* Thu Apr 25 2024 Jakub Panek
|
||||
- See GitHub for full changelog
|
||||
EOL
|
||||
|
||||
/usr/bin/rpmbuild --build-in-place --noclean --noprep -bb --rmspec lapce.spec --verbose -D 'debug_package %{nil}'
|
||||
ls -lahR $HOME/rpmbuild
|
||||
EOF
|
||||
|
||||
WORKDIR /output
|
||||
RUN <<EOF
|
||||
RUN \
|
||||
<<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
|
||||
export _PACKAGE_ARCHITECTURE=$(xx-info rhel-arch)
|
||||
|
||||
mkdir -v -p ${PACKAGE_NAME}/{etc,usr/bin}
|
||||
cd ${PACKAGE_NAME}
|
||||
|
||||
mv /target/lapce ./usr/bin/
|
||||
|
||||
if [ "${PACKAGE_NAME}" = "lapce" ]; then
|
||||
conflicts="lapce-nightly"
|
||||
else
|
||||
conflicts="lapce"
|
||||
fi
|
||||
|
||||
if [ "${RELEASE_TAG_NAME}" = "nightly" ]; then
|
||||
RELEASE_TAG_NAME="v0.0.1"
|
||||
fi
|
||||
|
||||
mv -v $HOME/rpmbuild/RPMS/* /output/
|
||||
EOF
|
||||
|
||||
FROM build-base AS dev
|
||||
COPY . ./dev
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build /output/*.deb .
|
||||
COPY --from=build /output/lapce .
|
||||
|
||||
FROM scratch AS cross-binary
|
||||
COPY --from=build /output/*.deb .
|
||||
COPY --from=build /output/lapce .
|
||||
|
||||
FROM scratch AS package
|
||||
COPY --from=build /output/*.rpm .
|
||||
|
||||
FROM scratch AS cross-package
|
||||
COPY --from=build /output/*.rpm .
|
||||
|
||||
@@ -126,8 +126,7 @@ xx-cargo build \
|
||||
--frozen \
|
||||
--package lapce-app \
|
||||
--profile release-lto \
|
||||
--no-default-features \
|
||||
--features all-languages
|
||||
--no-default-features
|
||||
|
||||
xx-verify "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapce
|
||||
|
||||
@@ -137,7 +136,18 @@ mv -v "${CARGO_TARGET_DIR}"/"$(xx-cargo --print-target-triple)"/release-lto/lapc
|
||||
cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2 | tee /target/lapce.version
|
||||
EOF
|
||||
|
||||
FROM build-base AS dev
|
||||
COPY . ./dev
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build /target/lapce .
|
||||
|
||||
FROM scratch AS cross-binary
|
||||
COPY --from=build /target/lapce .
|
||||
|
||||
FROM build AS package-prepare
|
||||
WORKDIR /output
|
||||
COPY --from=build /target /target
|
||||
RUN <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
@@ -151,10 +161,7 @@ cp '/source/extra/linux/dev.lapce.lapce.desktop' './usr/share/applications/dev.l
|
||||
cp '/source/extra/linux/dev.lapce.lapce.metainfo.xml' './usr/share/metainfo/dev.lapce.lapce.metainfo.xml'
|
||||
cp '/source/extra/images/logo.png' './usr/share/pixmaps/dev.lapce.lapce.png'
|
||||
|
||||
cp '/target/lapce' './usr/bin/'
|
||||
|
||||
# single binary
|
||||
cp /target/lapce /output/lapce
|
||||
mv '/target/lapce' './usr/bin/'
|
||||
|
||||
if [[ "${PACKAGE_NAME}" == "lapce" ]]; then
|
||||
conflicts="lapce-nightly"
|
||||
@@ -199,17 +206,8 @@ mv debian DEBIAN
|
||||
dpkg-deb --root-owner-group --build . "${OUTPUT_DIR}"/"${PACKAGE_NAME}.${ID}.${VERSION_CODENAME}.${_PACKAGE_ARCHITECTURE}.deb"
|
||||
EOF
|
||||
|
||||
FROM build-base AS dev
|
||||
COPY . ./dev
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build /output/lapce .
|
||||
|
||||
FROM scratch AS cross-binary
|
||||
COPY --from=build /output/lapce .
|
||||
|
||||
FROM scratch AS package
|
||||
COPY --from=build /output/*.deb .
|
||||
COPY --from=package-prepare /output/*.deb .
|
||||
|
||||
FROM scratch AS cross-package
|
||||
COPY --from=build /output/*.deb .
|
||||
COPY --from=package-prepare /output/*.deb .
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.1</string>
|
||||
<string>0.4.6</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ else
|
||||
fi
|
||||
|
||||
printf 'Decompressing gzip\n'
|
||||
gzip -d "${lapce_dir}/lapce-proxy-${os_name}-${arch_name}.gz"
|
||||
gzip -df "${lapce_dir}/lapce-proxy-${os_name}-${arch_name}.gz"
|
||||
|
||||
printf 'Renaming proxy \n'
|
||||
mv -v "${lapce_dir}/lapce-proxy-${os_name}-${arch_name}" "${lapce_dir}/lapce"
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
"drop-shadow-width": {
|
||||
"type": "integer"
|
||||
},
|
||||
"preview-editor-width": {
|
||||
"palette-width": {
|
||||
"type": "integer"
|
||||
},
|
||||
"hover-font-family": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="windows-1252"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
<Product Name="Lapce" Id="*" UpgradeCode="9c09a374-1135-4782-959f-2dec376a1dfa" Language="1033" Codepage="1252" Version="0.3.1" Manufacturer="Lapce">
|
||||
<Product Name="Lapce" Id="*" UpgradeCode="9c09a374-1135-4782-959f-2dec376a1dfa" Language="1033" Codepage="1252" Version="0.4.6" Manufacturer="Lapce">
|
||||
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine"/>
|
||||
<MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of [ProductName] is already installed."/>
|
||||
<Icon Id="lapce.exe" SourceFile=".\extra\windows\lapce.ico"/>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 1l-.5.5v3l.5.5h3l.5-.5v-3L4.5 1h-3zM2 4V2h2v2H2zm-.5 2l-.5.5v3l.5.5h3l.5-.5v-3L4.5 6h-3zM2 9V7h2v2H2zm-1 2.5l.5-.5h3l.5.5v3l-.5.5h-3l-.5-.5v-3zm1 .5v2h2v-2H2zm10.5-7l-.5.5v6l.5.5h3l.5-.5v-6l-.5-.5h-3zM15 8h-2V6h2v2zm0 3h-2V9h2v2zM9.1 8H6v1h3.1l-1 1 .7.6 1.8-1.8v-.7L8.8 6.3l-.7.7 1 1z"/></svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M13.5 12h-1.793L10 10.293V6.5L9.5 6H8V4h.5l.5-.5v-2L8.5 1h-2l-.5.5v2l.5.5H7v2H5.5l-.5.5v3.793L3.293 12H1.5l-.5.5v2l.5.5h2l.5-.5v-1.793L5.707 11h3.586L11 12.707V14.5l.5.5h2l.5-.5v-2l-.5-.5zM7 2h1v1H7V2zM6 7h3v3H6V7zm-3 7H2v-1h1v1zm10 0h-1v-1h1v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 366 B |
+20
-40
@@ -13,7 +13,6 @@ backtrace = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
directories = { workspace = true }
|
||||
flate2 = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
im = { workspace = true }
|
||||
@@ -34,10 +33,12 @@ reqwest = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
tar = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
toml_edit = { workspace = true }
|
||||
@@ -46,25 +47,21 @@ tracing-log = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-appender = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zstd = { workspace = true }
|
||||
floem = { workspace = true }
|
||||
|
||||
pulldown-cmark = "0.10.2"
|
||||
Inflector = "0.11.4"
|
||||
open = "5.1.2"
|
||||
unicode-width = "0.1.11"
|
||||
nucleo = "0.5.0"
|
||||
bytemuck = "1.14.3"
|
||||
config = { version = "=0.13.4", default-features = false, features = ["toml"] }
|
||||
structdesc = { git = "https://github.com/lapce/structdesc" }
|
||||
base64 = "0.21.7"
|
||||
sha2 = "0.10.8"
|
||||
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
|
||||
|
||||
[dependencies.floem]
|
||||
# path = "../../workspaces/floem"
|
||||
git = "https://github.com/lapce/floem"
|
||||
rev = "1477931896d14a9dafbfc8159e7a52a9d8e1d8b8"
|
||||
default-features = true
|
||||
features = ["editor", "serde", "default-image-formats", "rfd-async-std"]
|
||||
pulldown-cmark = { version = "0.11.0" }
|
||||
Inflector = { version = "0.11.4" }
|
||||
open = { version = "5.1.4" }
|
||||
unicode-width = { version = "0.1.13" }
|
||||
nucleo = { version = "0.5.0" }
|
||||
bytemuck = { version = "1.22.0" }
|
||||
config = { version = "=0.13.4", default-features = false, features = ["toml"] }
|
||||
structdesc = { git = "https://github.com/lapce/structdesc", rev = "bb56969f22fdb2c2d6c03f158fd4a2bdc983b659" }
|
||||
base64 = { version = "0.21.7" }
|
||||
sha2 = { version = "0.10.8" }
|
||||
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
|
||||
percent-encoding = { version = "2.3.1" }
|
||||
|
||||
[target.'cfg(target_os="macos")'.dependencies]
|
||||
fs_extra = "1.2.0"
|
||||
@@ -75,27 +72,10 @@ workspace = true
|
||||
features = ["Win32_Foundation", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging"]
|
||||
|
||||
[features]
|
||||
default = ["all-languages", "updater"]
|
||||
portable = ["lapce-core/portable"]
|
||||
updater = []
|
||||
|
||||
# To build lapce with only some of the supported languages, for example:
|
||||
#
|
||||
# cargo build --no-default-features -p lapce-ui \
|
||||
# --features lang-rust,lang-toml,lang-markdown,lang-json
|
||||
#
|
||||
all-languages = [
|
||||
"lapce-core/lang-bash",
|
||||
"lapce-core/lang-c",
|
||||
"lapce-core/lang-cpp",
|
||||
"lapce-core/lang-javascript",
|
||||
"lapce-core/lang-json",
|
||||
"lapce-core/lang-markdown",
|
||||
"lapce-core/lang-python",
|
||||
"lapce-core/lang-rust",
|
||||
"lapce-core/lang-toml",
|
||||
"lapce-core/lang-yaml",
|
||||
]
|
||||
default = ["updater", "vendored-fonts"]
|
||||
portable = ["lapce-core/portable"]
|
||||
updater = []
|
||||
vendored-fonts = []
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
||||
use floem::{
|
||||
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout, Wrap},
|
||||
reactive::Scope,
|
||||
text::{Attrs, AttrsList, FamilyOwned, TextLayout, Wrap},
|
||||
views::editor::{
|
||||
layout::TextLayoutLine,
|
||||
phantom_text::PhantomTextLine,
|
||||
@@ -28,7 +28,7 @@ struct TLProv<'a> {
|
||||
font_family: Vec<FamilyOwned>,
|
||||
has_multiline_phantom: bool,
|
||||
}
|
||||
impl<'a> TextLayoutProvider for TLProv<'a> {
|
||||
impl TextLayoutProvider for TLProv<'_> {
|
||||
fn text(&self) -> Rope {
|
||||
self.text.clone()
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl<'a> TextLayoutProvider for TLProv<'a> {
|
||||
let attrs = Attrs::new()
|
||||
.family(&self.font_family)
|
||||
.font_size(font_size as f32);
|
||||
let mut attrs_list = AttrsList::new(attrs);
|
||||
let mut attrs_list = AttrsList::new(attrs.clone());
|
||||
|
||||
// We don't do line styles, since they aren't relevant
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<'a> TextLayoutProvider for TLProv<'a> {
|
||||
let start = col + offset;
|
||||
let end = start + size;
|
||||
|
||||
let mut attrs = attrs;
|
||||
let mut attrs = attrs.clone();
|
||||
if let Some(fg) = phantom.fg {
|
||||
attrs = attrs.color(fg);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ impl<'a> TextLayoutProvider for TLProv<'a> {
|
||||
text_layout.set_size(px, f32::MAX);
|
||||
}
|
||||
}
|
||||
text_layout.set_text(&line_content, attrs_list);
|
||||
text_layout.set_text(&line_content, attrs_list, None);
|
||||
|
||||
// skip phantom text background styling because it doesn't shift positions
|
||||
// skip severity styling
|
||||
@@ -116,6 +116,7 @@ impl<'a> TextLayoutProvider for TLProv<'a> {
|
||||
text: text_layout,
|
||||
whitespaces: None,
|
||||
indent: 0.0,
|
||||
phantom_text: PhantomTextLine::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use floem::{
|
||||
View,
|
||||
event::EventListener,
|
||||
keyboard::Modifiers,
|
||||
reactive::{RwSignal, Scope},
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate},
|
||||
style::{CursorStyle, Display, Position},
|
||||
view::View,
|
||||
views::{container, label, stack, svg, Decorators},
|
||||
views::{Decorators, container, label, stack, svg},
|
||||
};
|
||||
use lapce_core::{command::FocusCommand, meta::VERSION, mode::Mode};
|
||||
|
||||
@@ -28,7 +28,7 @@ impl AboutUri {
|
||||
const CODICONS: &'static str = "https://github.com/microsoft/vscode-codicons";
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AboutData {
|
||||
pub visible: RwSignal<bool>,
|
||||
pub focus: RwSignal<Focus>,
|
||||
@@ -157,6 +157,7 @@ pub fn about_popup(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
))
|
||||
.style(|s| s.flex_col().items_center())
|
||||
})
|
||||
.debug_name("About Popup")
|
||||
}
|
||||
|
||||
fn exclusive_popup<V: View + 'static>(
|
||||
@@ -206,7 +207,7 @@ fn exclusive_popup<V: View + 'static>(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::LAPCE_DROPDOWN_SHADOW)
|
||||
.with_alpha_factor(0.5),
|
||||
.multiply_alpha(0.5),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use std::{
|
||||
fmt,
|
||||
rc::Rc,
|
||||
sync::{atomic::AtomicU64, Arc},
|
||||
sync::{Arc, atomic::AtomicU64},
|
||||
};
|
||||
|
||||
use floem::{
|
||||
View,
|
||||
event::EventListener,
|
||||
reactive::{ReadSignal, RwSignal, Scope},
|
||||
reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate},
|
||||
style::CursorStyle,
|
||||
view::View,
|
||||
views::{container, dyn_stack, label, stack, svg, Decorators},
|
||||
views::{Decorators, container, dyn_stack, label, stack, svg},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor, icon::LapceIcons},
|
||||
window_tab::CommonData,
|
||||
};
|
||||
|
||||
@@ -167,7 +167,8 @@ pub fn alert_box(alert_data: AlertBoxData) -> impl View {
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::LAPCE_DROPDOWN_SHADOW)
|
||||
.with_alpha_factor(0.5),
|
||||
.multiply_alpha(0.5),
|
||||
)
|
||||
})
|
||||
.debug_name("Alert Box")
|
||||
}
|
||||
|
||||
+1053
-703
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,141 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::{self},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use lapce_core::directory::Directory;
|
||||
|
||||
use crate::{tracing::*, update::ReleaseInfo};
|
||||
|
||||
fn get_github_api(url: &str) -> Result<String> {
|
||||
let user_agent = format!("Lapce/{}", lapce_core::meta::VERSION);
|
||||
let resp = lapce_proxy::get_url(url, Some(user_agent.as_str()))?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(anyhow!("get release info failed {}", resp.text()?));
|
||||
}
|
||||
|
||||
Ok(resp.text()?)
|
||||
}
|
||||
|
||||
pub fn find_grammar_release() -> Result<ReleaseInfo> {
|
||||
let releases: Vec<ReleaseInfo> = serde_json::from_str(&get_github_api(
|
||||
"https://api.github.com/repos/lapce/tree-sitter-grammars/releases?per_page=100",
|
||||
).context("Failed to retrieve releases for tree-sitter-grammars")?)?;
|
||||
|
||||
use lapce_core::meta::{RELEASE, ReleaseType, VERSION};
|
||||
|
||||
let releases = releases
|
||||
.into_iter()
|
||||
.filter_map(|f| {
|
||||
if matches!(RELEASE, ReleaseType::Debug | ReleaseType::Nightly) {
|
||||
return Some(f);
|
||||
}
|
||||
|
||||
let tag_name = if f.tag_name.starts_with('v') {
|
||||
f.tag_name.trim_start_matches('v')
|
||||
} else {
|
||||
f.tag_name.as_str()
|
||||
};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use semver::Version;
|
||||
|
||||
let sv = Version::parse(tag_name).ok()?;
|
||||
let version = Version::parse(VERSION).ok()?;
|
||||
|
||||
if matches!(sv.cmp_precedence(&version), Ordering::Equal) {
|
||||
Some(f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let Some(release) = releases.first() else {
|
||||
return Err(anyhow!("Couldn't find any release"));
|
||||
};
|
||||
|
||||
Ok(release.to_owned())
|
||||
}
|
||||
|
||||
pub fn fetch_grammars(release: &ReleaseInfo) -> Result<bool> {
|
||||
let dir = Directory::grammars_directory()
|
||||
.ok_or_else(|| anyhow!("can't get grammars directory"))?;
|
||||
|
||||
let file_name = format!("grammars-{}-{}", env::consts::OS, env::consts::ARCH);
|
||||
|
||||
let updated = download_release(dir, release, &file_name)?;
|
||||
|
||||
trace!(TraceLevel::INFO, "Successfully downloaded grammars");
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
pub fn fetch_queries(release: &ReleaseInfo) -> Result<bool> {
|
||||
let dir = Directory::queries_directory()
|
||||
.ok_or_else(|| anyhow!("can't get queries directory"))?;
|
||||
|
||||
let file_name = "queries";
|
||||
|
||||
let updated = download_release(dir, release, file_name)?;
|
||||
|
||||
trace!(TraceLevel::INFO, "Successfully downloaded queries");
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
fn download_release(
|
||||
dir: PathBuf,
|
||||
release: &ReleaseInfo,
|
||||
file_name: &str,
|
||||
) -> Result<bool> {
|
||||
if !dir.exists() {
|
||||
fs::create_dir(&dir)?;
|
||||
}
|
||||
|
||||
let current_version =
|
||||
fs::read_to_string(dir.join("version")).unwrap_or_default();
|
||||
let release_version = if release.tag_name == "nightly" {
|
||||
format!("nightly-{}", &release.target_commitish[..7])
|
||||
} else {
|
||||
release.tag_name.clone()
|
||||
};
|
||||
|
||||
if release_version == current_version {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for asset in &release.assets {
|
||||
if asset.name.starts_with(file_name) {
|
||||
let mut resp = lapce_proxy::get_url(&asset.browser_download_url, None)?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(anyhow!("download file error {}", resp.text()?));
|
||||
}
|
||||
|
||||
let file = tempfile::tempfile()?;
|
||||
|
||||
{
|
||||
use std::io::{Seek, Write};
|
||||
let file = &mut &file;
|
||||
resp.copy_to(file)?;
|
||||
file.flush()?;
|
||||
file.rewind()?;
|
||||
}
|
||||
|
||||
if asset.name.ends_with(".zip") {
|
||||
let mut archive = zip::ZipArchive::new(file)?;
|
||||
archive.extract(&dir)?;
|
||||
} else if asset.name.ends_with(".tar.zst") {
|
||||
let mut archive =
|
||||
tar::Archive::new(zstd::stream::read::Decoder::new(file)?);
|
||||
archive.unpack(&dir)?;
|
||||
}
|
||||
|
||||
fs::write(dir.join("version"), &release_version)?;
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use lapce_core::directory::Directory;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::{filter::Targets, reload::Handle};
|
||||
|
||||
use crate::tracing::*;
|
||||
|
||||
#[inline(always)]
|
||||
pub(super) fn logging() -> (Handle<Targets>, Option<WorkerGuard>) {
|
||||
use tracing_subscriber::{filter, fmt, prelude::*, reload};
|
||||
|
||||
let (log_file, guard) = match Directory::logs_directory()
|
||||
.and_then(|dir| {
|
||||
tracing_appender::rolling::Builder::new()
|
||||
.max_log_files(10)
|
||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||
.filename_prefix("lapce")
|
||||
.filename_suffix("log")
|
||||
.build(dir)
|
||||
.ok()
|
||||
})
|
||||
.map(tracing_appender::non_blocking)
|
||||
{
|
||||
Some((log_file, guard)) => (Some(log_file), Some(guard)),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let log_file_filter_targets = filter::Targets::new()
|
||||
.with_target("lapce_app", LevelFilter::DEBUG)
|
||||
.with_target("lapce_proxy", LevelFilter::DEBUG)
|
||||
.with_target("lapce_core", LevelFilter::DEBUG)
|
||||
.with_default(LevelFilter::from_level(TraceLevel::INFO));
|
||||
let (log_file_filter, reload_handle) =
|
||||
reload::Subscriber::new(log_file_filter_targets);
|
||||
|
||||
let console_filter_targets = std::env::var("LAPCE_LOG")
|
||||
.unwrap_or_default()
|
||||
.parse::<filter::Targets>()
|
||||
.unwrap_or_default();
|
||||
|
||||
let registry = tracing_subscriber::registry();
|
||||
if let Some(log_file) = log_file {
|
||||
let file_layer = tracing_subscriber::fmt::subscriber()
|
||||
.with_ansi(false)
|
||||
.with_writer(log_file)
|
||||
.with_filter(log_file_filter);
|
||||
registry
|
||||
.with(file_layer)
|
||||
.with(
|
||||
fmt::Subscriber::default()
|
||||
.with_line_number(true)
|
||||
.with_target(true)
|
||||
.with_thread_names(true)
|
||||
.with_filter(console_filter_targets),
|
||||
)
|
||||
.init();
|
||||
} else {
|
||||
registry
|
||||
.with(fmt::Subscriber::default().with_filter(console_filter_targets))
|
||||
.init();
|
||||
};
|
||||
|
||||
(reload_handle, guard)
|
||||
}
|
||||
|
||||
pub(super) fn panic_hook() {
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
let thread = std::thread::current();
|
||||
let thread = thread.name().unwrap_or("main");
|
||||
let backtrace = backtrace::Backtrace::new();
|
||||
|
||||
let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
|
||||
s
|
||||
} else {
|
||||
"<unknown>"
|
||||
};
|
||||
|
||||
match info.location() {
|
||||
Some(loc) => {
|
||||
trace!(
|
||||
target: "lapce_app::panic_hook",
|
||||
TraceLevel::ERROR,
|
||||
"thread {thread} panicked at {} | file://./{}:{}:{}\n{:?}",
|
||||
payload,
|
||||
loc.file(), loc.line(), loc.column(),
|
||||
backtrace,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
trace!(
|
||||
target: "lapce_app::panic_hook",
|
||||
TraceLevel::ERROR,
|
||||
"thread {thread} panicked at {}\n{:?}",
|
||||
payload,
|
||||
backtrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
error_modal("Error", &info.to_string());
|
||||
|
||||
#[cfg(unix)]
|
||||
error_notification("Error", &info.to_string());
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(super) fn error_modal(title: &str, msg: &str) -> i32 {
|
||||
use std::{ffi::OsStr, iter::once, mem, os::windows::prelude::OsStrExt};
|
||||
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
MB_ICONERROR, MB_SYSTEMMODAL, MessageBoxW,
|
||||
};
|
||||
|
||||
let result: i32;
|
||||
|
||||
let title = OsStr::new(title)
|
||||
.encode_wide()
|
||||
.chain(once(0u16))
|
||||
.collect::<Vec<u16>>();
|
||||
let msg = OsStr::new(msg)
|
||||
.encode_wide()
|
||||
.chain(once(0u16))
|
||||
.collect::<Vec<u16>>();
|
||||
unsafe {
|
||||
result = MessageBoxW(
|
||||
mem::zeroed(),
|
||||
msg.as_ptr(),
|
||||
title.as_ptr(),
|
||||
MB_ICONERROR | MB_SYSTEMMODAL,
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn error_notification(title: &str, msg: &str) {
|
||||
let res = std::process::Command::new("notify-send")
|
||||
.args([
|
||||
"-a",
|
||||
"dev.lapce.lapce",
|
||||
"-w",
|
||||
"-n",
|
||||
"dev.lapce.lapce",
|
||||
"-c",
|
||||
"error",
|
||||
title,
|
||||
msg,
|
||||
])
|
||||
.spawn();
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use lapce_app::app;
|
||||
|
||||
pub fn main() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use std::rc::Rc;
|
||||
|
||||
use floem::{
|
||||
keyboard::Modifiers,
|
||||
peniko::kurbo::Rect,
|
||||
reactive::{RwSignal, Scope},
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate},
|
||||
};
|
||||
use lapce_core::{command::FocusCommand, mode::Mode, movement::Movement};
|
||||
use lapce_rpc::plugin::PluginId;
|
||||
@@ -11,7 +11,7 @@ use lsp_types::CodeActionOrCommand;
|
||||
|
||||
use crate::{
|
||||
command::{CommandExecuted, CommandKind, InternalCommand},
|
||||
keypress::{condition::Condition, KeyPressFocus},
|
||||
keypress::{KeyPressFocus, condition::Condition},
|
||||
window_tab::{CommonData, Focus},
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ pub enum CodeActionStatus {
|
||||
Active,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ScoredCodeActionItem {
|
||||
pub item: CodeActionOrCommand,
|
||||
pub plugin_id: PluginId,
|
||||
@@ -38,7 +38,7 @@ impl ScoredCodeActionItem {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeActionData {
|
||||
pub status: RwSignal<CodeActionStatus>,
|
||||
pub active: RwSignal<usize>,
|
||||
@@ -166,7 +166,8 @@ impl CodeActionData {
|
||||
|
||||
pub fn show(
|
||||
&mut self,
|
||||
code_actions: Arc<(PluginId, Vec<CodeActionOrCommand>)>,
|
||||
plugin_id: PluginId,
|
||||
code_actions: im::Vector<CodeActionOrCommand>,
|
||||
offset: usize,
|
||||
mouse_click: bool,
|
||||
) {
|
||||
@@ -176,11 +177,10 @@ impl CodeActionData {
|
||||
self.mouse_click = mouse_click;
|
||||
self.request_id += 1;
|
||||
self.items = code_actions
|
||||
.1
|
||||
.iter()
|
||||
.into_iter()
|
||||
.map(|code_action| ScoredCodeActionItem {
|
||||
item: code_action.clone(),
|
||||
plugin_id: code_actions.0,
|
||||
item: code_action,
|
||||
plugin_id,
|
||||
score: 0,
|
||||
indices: Vec::new(),
|
||||
})
|
||||
@@ -194,7 +194,7 @@ impl CodeActionData {
|
||||
self.common.focus.set(Focus::Workbench);
|
||||
}
|
||||
|
||||
fn select(&self) {
|
||||
pub fn select(&self) {
|
||||
if let Some(item) = self.filtered_items.get(self.active.get_untracked()) {
|
||||
self.common
|
||||
.internal_command
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use lapce_rpc::dap_types::{ConfigSource, RunDebugConfig};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{command::InternalCommand, debug::RunDebugMode, window_tab::CommonData};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CargoArgs {
|
||||
#[serde(rename = "cargoArgs")]
|
||||
pub cargo_args: Vec<String>,
|
||||
|
||||
#[serde(rename = "cargoExtraArgs")]
|
||||
pub cargo_extra_args: Vec<String>,
|
||||
|
||||
#[serde(rename = "executableArgs")]
|
||||
pub executable_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RustArgs {
|
||||
pub args: CargoArgs,
|
||||
pub kind: String,
|
||||
pub label: String,
|
||||
pub location: lsp_types::LocationLink,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CodeLensData {
|
||||
common: Rc<CommonData>,
|
||||
}
|
||||
|
||||
impl CodeLensData {
|
||||
pub fn new(common: Rc<CommonData>) -> Self {
|
||||
Self { common }
|
||||
}
|
||||
|
||||
pub fn run(&self, command: &str, args: Vec<Value>) {
|
||||
match command {
|
||||
"rust-analyzer.runSingle" | "rust-analyzer.debugSingle" => {
|
||||
let mode = if command == "rust-analyzer.runSingle" {
|
||||
RunDebugMode::Run
|
||||
} else {
|
||||
RunDebugMode::Debug
|
||||
};
|
||||
if let Some(config) = self.get_rust_command_config(&args, mode) {
|
||||
self.common
|
||||
.internal_command
|
||||
.send(InternalCommand::RunAndDebug { mode, config });
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing::debug!("todo {:}", command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rust_command_config(
|
||||
&self,
|
||||
args: &[Value],
|
||||
mode: RunDebugMode,
|
||||
) -> Option<RunDebugConfig> {
|
||||
if let Some(args) = args.first() {
|
||||
let Ok(mut cargo_args) =
|
||||
serde_json::from_value::<RustArgs>(args.clone())
|
||||
else {
|
||||
tracing::error!("serde error");
|
||||
return None;
|
||||
};
|
||||
cargo_args
|
||||
.args
|
||||
.cargo_args
|
||||
.extend(cargo_args.args.cargo_extra_args);
|
||||
if !cargo_args.args.executable_args.is_empty() {
|
||||
cargo_args.args.cargo_args.push("--".to_string());
|
||||
cargo_args
|
||||
.args
|
||||
.cargo_args
|
||||
.extend(cargo_args.args.executable_args);
|
||||
}
|
||||
Some(RunDebugConfig {
|
||||
ty: None,
|
||||
name: cargo_args.label,
|
||||
program: cargo_args.kind,
|
||||
args: Some(cargo_args.args.cargo_args),
|
||||
cwd: None,
|
||||
env: None,
|
||||
prelaunch: None,
|
||||
debug_command: None,
|
||||
dap_id: Default::default(),
|
||||
tracing_output: mode == RunDebugMode::Debug,
|
||||
config_source: ConfigSource::CodeLens,
|
||||
})
|
||||
} else {
|
||||
tracing::error!("no args");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::{path::PathBuf, rc::Rc, sync::Arc};
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
|
||||
pub use floem::views::editor::command::CommandExecuted;
|
||||
use floem::{
|
||||
keyboard::Modifiers, peniko::kurbo::Vec2, views::editor::command::Command,
|
||||
ViewId, keyboard::Modifiers, peniko::kurbo::Vec2,
|
||||
views::editor::command::Command,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use lapce_core::command::{
|
||||
@@ -17,7 +19,7 @@ use lapce_rpc::{
|
||||
use lsp_types::{CodeActionOrCommand, Position, WorkspaceEdit};
|
||||
use serde_json::Value;
|
||||
use strum::{EnumMessage, IntoEnumIterator};
|
||||
use strum_macros::{Display, EnumIter, EnumMessage, EnumString, IntoStaticStr};
|
||||
use strum_macros::{Display, EnumIter, EnumString, IntoStaticStr};
|
||||
|
||||
use crate::{
|
||||
alert::AlertButton,
|
||||
@@ -26,12 +28,10 @@ use crate::{
|
||||
editor::location::EditorLocation,
|
||||
editor_tab::EditorTabChild,
|
||||
id::EditorTabId,
|
||||
main_split::{SplitDirection, SplitMoveDirection},
|
||||
main_split::{SplitDirection, SplitMoveDirection, TabCloseKind},
|
||||
workspace::LapceWorkspace,
|
||||
};
|
||||
|
||||
pub use floem::views::editor::command::CommandExecuted;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LapceCommand {
|
||||
pub kind: CommandKind,
|
||||
@@ -183,6 +183,40 @@ pub enum LapceWorkbenchCommand {
|
||||
#[strum(message = "Open File")]
|
||||
OpenFile,
|
||||
|
||||
#[strum(serialize = "show_call_hierarchy")]
|
||||
#[strum(message = "Show Call Hierarchy")]
|
||||
ShowCallHierarchy,
|
||||
|
||||
#[strum(serialize = "find_references")]
|
||||
#[strum(message = "Find References")]
|
||||
FindReferences,
|
||||
|
||||
#[strum(serialize = "go_to_implementation")]
|
||||
#[strum(message = "Go to Implementation")]
|
||||
GoToImplementation,
|
||||
|
||||
#[strum(serialize = "reveal_in_panel")]
|
||||
#[strum(message = "Reveal in Panel")]
|
||||
RevealInPanel,
|
||||
|
||||
#[strum(serialize = "source_control_open_active_file_remote_url")]
|
||||
#[strum(message = "Source Control: Open Remote File Url")]
|
||||
SourceControlOpenActiveFileRemoteUrl,
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[strum(serialize = "reveal_in_file_explorer")]
|
||||
#[strum(message = "Reveal in System File Explorer")]
|
||||
RevealInFileExplorer,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[strum(serialize = "reveal_in_file_explorer")]
|
||||
#[strum(message = "Reveal in Finder")]
|
||||
RevealInFileExplorer,
|
||||
|
||||
#[strum(serialize = "run_in_terminal")]
|
||||
#[strum(message = "Run in Terminal")]
|
||||
RunInTerminal,
|
||||
|
||||
#[strum(serialize = "reveal_active_file_in_file_explorer")]
|
||||
#[strum(message = "Reveal Active File in File Explorer")]
|
||||
RevealActiveFileInFileExplorer,
|
||||
@@ -191,6 +225,10 @@ pub enum LapceWorkbenchCommand {
|
||||
#[strum(message = "Open Internal UI Inspector")]
|
||||
OpenUIInspector,
|
||||
|
||||
#[strum(serialize = "show_env")]
|
||||
#[strum(message = "Show Environment")]
|
||||
ShowEnvironment,
|
||||
|
||||
#[strum(serialize = "change_color_theme")]
|
||||
#[strum(message = "Change Color Theme")]
|
||||
ChangeColorTheme,
|
||||
@@ -243,6 +281,14 @@ pub enum LapceWorkbenchCommand {
|
||||
#[strum(message = "Open Plugins Directory")]
|
||||
OpenPluginsDirectory,
|
||||
|
||||
#[strum(serialize = "open_grammars_directory")]
|
||||
#[strum(message = "Open Grammars Directory")]
|
||||
OpenGrammarsDirectory,
|
||||
|
||||
#[strum(serialize = "open_queries_directory")]
|
||||
#[strum(message = "Open Queries Directory")]
|
||||
OpenQueriesDirectory,
|
||||
|
||||
#[strum(serialize = "zoom_in")]
|
||||
#[strum(message = "Zoom In")]
|
||||
ZoomIn,
|
||||
@@ -352,6 +398,10 @@ pub enum LapceWorkbenchCommand {
|
||||
#[strum(serialize = "palette.palette_help")]
|
||||
PaletteHelp,
|
||||
|
||||
#[strum(message = "List Palette Types and Files")]
|
||||
#[strum(serialize = "palette.palette_help_and_file")]
|
||||
PaletteHelpAndFile,
|
||||
|
||||
#[strum(message = "Run and Debug Restart Current Running")]
|
||||
#[strum(serialize = "palette.run_and_debug_restart")]
|
||||
RunAndDebugRestart,
|
||||
@@ -542,6 +592,14 @@ pub enum LapceWorkbenchCommand {
|
||||
#[strum(serialize = "quit")]
|
||||
#[strum(message = "Quit Editor")]
|
||||
Quit,
|
||||
|
||||
#[strum(serialize = "go_to_location")]
|
||||
#[strum(message = "Go to Location")]
|
||||
GoToLocation,
|
||||
|
||||
#[strum(serialize = "add_run_debug_config")]
|
||||
#[strum(message = "Add Run Debug Config")]
|
||||
AddRunDebugConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -550,6 +608,9 @@ pub enum InternalCommand {
|
||||
OpenFile {
|
||||
path: PathBuf,
|
||||
},
|
||||
OpenAndConfirmedFile {
|
||||
path: PathBuf,
|
||||
},
|
||||
OpenFileInNewTab {
|
||||
path: PathBuf,
|
||||
},
|
||||
@@ -621,10 +682,16 @@ pub enum InternalCommand {
|
||||
editor_tab_id: EditorTabId,
|
||||
child: EditorTabChild,
|
||||
},
|
||||
EditorTabCloseByKind {
|
||||
editor_tab_id: EditorTabId,
|
||||
child: EditorTabChild,
|
||||
kind: TabCloseKind,
|
||||
},
|
||||
ShowCodeActions {
|
||||
offset: usize,
|
||||
mouse_click: bool,
|
||||
code_actions: Arc<(PluginId, Vec<CodeActionOrCommand>)>,
|
||||
plugin_id: PluginId,
|
||||
code_actions: im::Vector<CodeActionOrCommand>,
|
||||
},
|
||||
RunCodeAction {
|
||||
plugin_id: PluginId,
|
||||
@@ -712,6 +779,24 @@ pub enum InternalCommand {
|
||||
left_path: PathBuf,
|
||||
right_path: PathBuf,
|
||||
},
|
||||
ExecuteProcess {
|
||||
program: String,
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
ClearTerminalBuffer {
|
||||
view_id: ViewId,
|
||||
tab_index: usize,
|
||||
terminal_index: usize,
|
||||
},
|
||||
CallHierarchyIncoming {
|
||||
item_id: ViewId,
|
||||
},
|
||||
StopTerminal {
|
||||
term_id: TermId,
|
||||
},
|
||||
RestartTerminal {
|
||||
term_id: TermId,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{borrow::Cow, path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
peniko::kurbo::Rect,
|
||||
reactive::{ReadSignal, RwSignal, Scope},
|
||||
reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
views::editor::{id::EditorId, text::Document},
|
||||
};
|
||||
use lapce_core::{
|
||||
|
||||
+93
-42
@@ -1,11 +1,11 @@
|
||||
use ::core::slice;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use floem::peniko::Color;
|
||||
use ::core::slice;
|
||||
use floem::{peniko::Color, prelude::palette::css};
|
||||
use itertools::Itertools;
|
||||
use lapce_core::directory::Directory;
|
||||
use lapce_proxy::plugin::wasi::find_all_volts;
|
||||
@@ -21,7 +21,7 @@ use self::{
|
||||
color::LapceColor,
|
||||
color_theme::{ColorThemeConfig, ThemeColor, ThemeColorPreference},
|
||||
core::CoreConfig,
|
||||
editor::{EditorConfig, WrapStyle, SCALE_OR_SIZE_LIMIT},
|
||||
editor::{EditorConfig, SCALE_OR_SIZE_LIMIT, WrapStyle},
|
||||
icon::LapceIcons,
|
||||
icon_theme::IconThemeConfig,
|
||||
svg::SvgStore,
|
||||
@@ -68,10 +68,18 @@ static DEFAULT_DARK_THEME_COLOR_CONFIG: Lazy<ColorThemeConfig> = Lazy::new(|| {
|
||||
theme.get::<ColorThemeConfig>("color-theme")
|
||||
.expect("Failed to load default dark theme. This is likely due to a missing or misnamed field in dark-theme.toml")
|
||||
});
|
||||
static DEFAULT_ICON_THEME_CONFIG: Lazy<IconThemeConfig> = Lazy::new(|| {
|
||||
let (_, theme) =
|
||||
LapceConfig::load_icon_theme_from_str(DEFAULT_ICON_THEME).unwrap();
|
||||
theme.get::<IconThemeConfig>("icon-theme")
|
||||
|
||||
static DEFAULT_ICON_THEME_CONFIG: Lazy<config::Config> = Lazy::new(|| {
|
||||
config::Config::builder()
|
||||
.add_source(config::File::from_str(
|
||||
DEFAULT_ICON_THEME,
|
||||
config::FileFormat::Toml,
|
||||
))
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
static DEFAULT_ICON_THEME_ICON_CONFIG: Lazy<IconThemeConfig> = Lazy::new(|| {
|
||||
DEFAULT_ICON_THEME_CONFIG.get::<IconThemeConfig>("icon-theme")
|
||||
.expect("Failed to load default icon theme. This is likely due to a missing or misnamed field in icon-theme.toml")
|
||||
});
|
||||
|
||||
@@ -245,7 +253,7 @@ impl LapceConfig {
|
||||
let mut default_lapce_config: LapceConfig =
|
||||
DEFAULT_CONFIG.clone().try_deserialize().expect("Failed to deserialize default config, this likely indicates a missing or misnamed field in settings.toml");
|
||||
default_lapce_config.color_theme = DEFAULT_DARK_THEME_COLOR_CONFIG.clone();
|
||||
default_lapce_config.icon_theme = DEFAULT_ICON_THEME_CONFIG.clone();
|
||||
default_lapce_config.icon_theme = DEFAULT_ICON_THEME_ICON_CONFIG.clone();
|
||||
default_lapce_config.resolve_colors(None);
|
||||
default_lapce_config
|
||||
}
|
||||
@@ -256,40 +264,39 @@ impl LapceConfig {
|
||||
let color_theme_config = self
|
||||
.available_color_themes
|
||||
.get(&self.core.color_theme.to_lowercase())
|
||||
.map(|(_, config)| config);
|
||||
.map(|(_, config)| config)
|
||||
.unwrap_or(&DEFAULT_DARK_THEME_CONFIG);
|
||||
|
||||
let icon_theme_config = self
|
||||
.available_icon_themes
|
||||
.get(&self.core.icon_theme.to_lowercase())
|
||||
.map(|(_, config, _)| config);
|
||||
.map(|(_, config, _)| config)
|
||||
.unwrap_or(&DEFAULT_ICON_THEME_CONFIG);
|
||||
|
||||
let icon_theme_path = self
|
||||
.available_icon_themes
|
||||
.get(&self.core.icon_theme.to_lowercase())
|
||||
.map(|(_, _, path)| path);
|
||||
|
||||
if color_theme_config.is_some() || icon_theme_config.is_some() {
|
||||
if let Ok(new) = Self::merge_config(
|
||||
workspace,
|
||||
color_theme_config.cloned(),
|
||||
icon_theme_config.cloned(),
|
||||
)
|
||||
.try_deserialize::<LapceConfig>()
|
||||
{
|
||||
self.core = new.core;
|
||||
self.ui = new.ui;
|
||||
self.editor = new.editor;
|
||||
self.terminal = new.terminal;
|
||||
self.terminal.get_indexed_colors();
|
||||
if let Ok(new) = Self::merge_config(
|
||||
workspace,
|
||||
Some(color_theme_config.clone()),
|
||||
Some(icon_theme_config.clone()),
|
||||
)
|
||||
.try_deserialize::<LapceConfig>()
|
||||
{
|
||||
self.core = new.core;
|
||||
self.ui = new.ui;
|
||||
self.editor = new.editor;
|
||||
self.terminal = new.terminal;
|
||||
self.terminal.get_indexed_colors();
|
||||
|
||||
self.color_theme = new.color_theme;
|
||||
self.icon_theme = new.icon_theme;
|
||||
if let Some(icon_theme_path) = icon_theme_path {
|
||||
self.icon_theme.path =
|
||||
icon_theme_path.clone().unwrap_or_default();
|
||||
}
|
||||
self.plugins = new.plugins;
|
||||
self.color_theme = new.color_theme;
|
||||
self.icon_theme = new.icon_theme;
|
||||
if let Some(icon_theme_path) = icon_theme_path {
|
||||
self.icon_theme.path = icon_theme_path.clone().unwrap_or_default();
|
||||
}
|
||||
self.plugins = new.plugins;
|
||||
}
|
||||
self.resolve_colors(Some(&default_lapce_config));
|
||||
self.update_id();
|
||||
@@ -349,7 +356,7 @@ impl LapceConfig {
|
||||
Some(c) => *c,
|
||||
None => {
|
||||
error!("Failed to find key: {name}");
|
||||
Color::HOT_PINK
|
||||
css::HOT_PINK
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,8 +405,8 @@ impl LapceConfig {
|
||||
default_config.map(|c| &c.color.syntax),
|
||||
);
|
||||
|
||||
let fg = self.color(LapceColor::EDITOR_FOREGROUND);
|
||||
let bg = self.color(LapceColor::EDITOR_BACKGROUND);
|
||||
let fg = self.color(LapceColor::EDITOR_FOREGROUND).to_rgba8();
|
||||
let bg = self.color(LapceColor::EDITOR_BACKGROUND).to_rgba8();
|
||||
let is_light = fg.r as u32 + fg.g as u32 + fg.b as u32
|
||||
> bg.r as u32 + bg.g as u32 + bg.b as u32;
|
||||
let high_contrast = self.color_theme.high_contrast.unwrap_or(false);
|
||||
@@ -559,10 +566,13 @@ impl LapceConfig {
|
||||
let path = Directory::config_directory()?.join("settings.toml");
|
||||
|
||||
if !path.exists() {
|
||||
let _ = std::fs::OpenOptions::new()
|
||||
if let Err(err) = std::fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&path);
|
||||
.open(&path)
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Some(path)
|
||||
@@ -572,10 +582,13 @@ impl LapceConfig {
|
||||
let path = Directory::config_directory()?.join("keymaps.toml");
|
||||
|
||||
if !path.exists() {
|
||||
let _ = std::fs::OpenOptions::new()
|
||||
if let Err(err) = std::fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&path);
|
||||
.open(&path)
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Some(path)
|
||||
@@ -588,7 +601,7 @@ impl LapceConfig {
|
||||
});
|
||||
|
||||
svg.unwrap_or_else(|| {
|
||||
let name = DEFAULT_ICON_THEME_CONFIG.ui.get(icon).unwrap();
|
||||
let name = DEFAULT_ICON_THEME_ICON_CONFIG.ui.get(icon).unwrap();
|
||||
self.svg_store.write().get_default_svg(name)
|
||||
})
|
||||
}
|
||||
@@ -648,6 +661,36 @@ impl LapceConfig {
|
||||
Some(self.ui_svg(kind_str))
|
||||
}
|
||||
|
||||
pub fn symbol_color(&self, kind: &SymbolKind) -> Option<Color> {
|
||||
let theme_str = match *kind {
|
||||
SymbolKind::METHOD => "method",
|
||||
SymbolKind::FUNCTION => "method",
|
||||
SymbolKind::ENUM => "enum",
|
||||
SymbolKind::ENUM_MEMBER => "enum-member",
|
||||
SymbolKind::CLASS => "class",
|
||||
SymbolKind::VARIABLE => "field",
|
||||
SymbolKind::STRUCT => "structure",
|
||||
SymbolKind::CONSTANT => "constant",
|
||||
SymbolKind::PROPERTY => "property",
|
||||
SymbolKind::FIELD => "field",
|
||||
SymbolKind::INTERFACE => "interface",
|
||||
SymbolKind::ARRAY => "",
|
||||
SymbolKind::BOOLEAN => "",
|
||||
SymbolKind::EVENT => "",
|
||||
SymbolKind::FILE => "",
|
||||
SymbolKind::KEY => "",
|
||||
SymbolKind::OBJECT => "",
|
||||
SymbolKind::NAMESPACE => "",
|
||||
SymbolKind::NUMBER => "number",
|
||||
SymbolKind::OPERATOR => "",
|
||||
SymbolKind::TYPE_PARAMETER => "",
|
||||
SymbolKind::STRING => "string",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
self.style_color(theme_str)
|
||||
}
|
||||
|
||||
pub fn logo_svg(&self) -> String {
|
||||
self.svg_store.read().logo_svg()
|
||||
}
|
||||
@@ -705,11 +748,11 @@ impl LapceConfig {
|
||||
self.terminal_get_named_color(color)
|
||||
}
|
||||
alacritty_terminal::vte::ansi::Color::Spec(rgb) => {
|
||||
Color::rgb8(rgb.r, rgb.g, rgb.b)
|
||||
Color::from_rgb8(rgb.r, rgb.g, rgb.b)
|
||||
}
|
||||
alacritty_terminal::vte::ansi::Color::Indexed(index) => {
|
||||
if let Some(rgb) = colors[*index as usize] {
|
||||
return Color::rgb8(rgb.r, rgb.g, rgb.b);
|
||||
return Color::from_rgb8(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
const NAMED_COLORS: [alacritty_terminal::vte::ansi::NamedColor; 16] = [
|
||||
alacritty_terminal::vte::ansi::NamedColor::Black,
|
||||
@@ -831,7 +874,7 @@ impl LapceConfig {
|
||||
(LapceColor::TERMINAL_FOREGROUND, 0.66)
|
||||
}
|
||||
};
|
||||
self.color(color).with_alpha_factor(alpha)
|
||||
self.color(color).multiply_alpha(alpha)
|
||||
}
|
||||
|
||||
/// Get the dropdown information for the specific setting, used for the settings UI.
|
||||
@@ -875,6 +918,14 @@ impl LapceConfig {
|
||||
.sorted()
|
||||
.collect(),
|
||||
}),
|
||||
("ui", "tab-separator-height") => Some(DropdownInfo {
|
||||
active_index: self.ui.tab_separator_height as usize,
|
||||
items: ui::TabSeparatorHeight::VARIANTS
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.sorted()
|
||||
.collect(),
|
||||
}),
|
||||
("terminal", "default-profile") => Some(DropdownInfo {
|
||||
active_index: self
|
||||
.terminal
|
||||
@@ -885,7 +936,7 @@ impl LapceConfig {
|
||||
== self
|
||||
.terminal
|
||||
.default_profile
|
||||
.get(&std::env::consts::OS.to_string())
|
||||
.get(std::env::consts::OS)
|
||||
.unwrap_or(&String::from("default"))
|
||||
})
|
||||
.unwrap_or(0),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use floem::peniko::Color;
|
||||
use floem::{peniko::Color, prelude::palette::css};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::color::LoadThemeError;
|
||||
@@ -37,7 +38,7 @@ pub struct ThemeColor {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
pub struct ThemeBaseConfig(BTreeMap<String, String>);
|
||||
pub struct ThemeBaseConfig(pub BTreeMap<String, String>);
|
||||
|
||||
impl ThemeBaseConfig {
|
||||
/// Resolve the variables in this theme base config into the actual colors.
|
||||
@@ -61,12 +62,12 @@ impl ThemeBaseConfig {
|
||||
for (key, value) in self.0.iter() {
|
||||
match self.resolve_variable(&default, key, value, 0) {
|
||||
Ok(Some(color)) => {
|
||||
let color = Color::parse(color)
|
||||
.unwrap_or_else(|| {
|
||||
let color = Color::from_str(color)
|
||||
.unwrap_or_else(|_| {
|
||||
tracing::warn!(
|
||||
"Failed to parse color theme variable for ({key}: {value})"
|
||||
);
|
||||
Color::HOT_PINK
|
||||
css::HOT_PINK
|
||||
});
|
||||
base.0.insert(key.to_string(), color);
|
||||
}
|
||||
@@ -148,14 +149,14 @@ impl ColorThemeConfig {
|
||||
let color = if let Some(stripped) = hex.strip_prefix('$') {
|
||||
base.get(stripped)
|
||||
} else {
|
||||
Color::parse(hex)
|
||||
Color::from_str(hex).ok()
|
||||
};
|
||||
|
||||
let color = color
|
||||
.or_else(|| {
|
||||
default.and_then(|default| default.get(name).cloned())
|
||||
})
|
||||
.unwrap_or(Color::rgb8(0, 0, 0));
|
||||
.unwrap_or(Color::from_rgb8(0, 0, 0));
|
||||
|
||||
(name.to_string(), color)
|
||||
})
|
||||
@@ -182,7 +183,7 @@ impl ColorThemeConfig {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use config::Config;
|
||||
use floem::peniko::Color;
|
||||
use floem::{peniko::Color, prelude::palette::css};
|
||||
|
||||
use crate::{config::LapceConfig, workspace::LapceWorkspace};
|
||||
|
||||
@@ -229,7 +230,7 @@ color-preference = "dark"
|
||||
|
||||
lapce_config.resolve_theme(&workspace);
|
||||
|
||||
println!("Hot Pink: {:?}", Color::HOT_PINK);
|
||||
println!("Hot Pink: {:?}", css::HOT_PINK);
|
||||
// test basic override
|
||||
assert_eq!(
|
||||
lapce_config.color("lapce.error"),
|
||||
@@ -239,13 +240,13 @@ color-preference = "dark"
|
||||
// test that it falls through to the dark theme for unspecified color
|
||||
assert_eq!(
|
||||
lapce_config.color("lapce.warn"),
|
||||
Color::rgb8(0xE5, 0xC0, 0x7B),
|
||||
Color::from_rgb8(0xE5, 0xC0, 0x7B),
|
||||
"Failed to get from fallback dark theme"
|
||||
); // $yellow
|
||||
// test that our custom variable worked
|
||||
// test that our custom variable worked
|
||||
assert_eq!(
|
||||
lapce_config.color("editor.background"),
|
||||
Color::rgb8(0xFF, 0x00, 0xFF),
|
||||
Color::from_rgb8(0xFF, 0x00, 0xFF),
|
||||
"Failed to get from custom variable"
|
||||
);
|
||||
// test that for text it now uses our redeclared variable
|
||||
|
||||
@@ -14,4 +14,12 @@ pub struct CoreConfig {
|
||||
desc = "Enable customised titlebar and disable OS native one (Linux, BSD, Windows)"
|
||||
)]
|
||||
pub custom_titlebar: bool,
|
||||
#[field_names(
|
||||
desc = "Only allow double-click to open files in the file explorer"
|
||||
)]
|
||||
pub file_explorer_double_click: bool,
|
||||
#[field_names(
|
||||
desc = "Enable auto-reload for the plugin when its configuration changes."
|
||||
)]
|
||||
pub auto_reload_plugin: bool,
|
||||
}
|
||||
|
||||
@@ -48,9 +48,12 @@ impl WrapStyle {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToString for WrapStyle {
|
||||
fn to_string(&self) -> String {
|
||||
self.as_str().to_string()
|
||||
|
||||
impl std::fmt::Display for WrapStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +64,8 @@ pub struct EditorConfig {
|
||||
pub font_family: String,
|
||||
#[field_names(desc = "Set the editor font size")]
|
||||
font_size: usize,
|
||||
#[field_names(desc = "Set the font size in the code lens")]
|
||||
pub code_lens_font_size: usize,
|
||||
#[field_names(desc = "Set the font size in the code glance")]
|
||||
pub code_glance_font_size: usize,
|
||||
#[field_names(
|
||||
desc = "Set the editor line height. If less than 5.0, line height will be a multiple of the font size."
|
||||
)]
|
||||
@@ -93,10 +96,16 @@ pub struct EditorConfig {
|
||||
desc = "Show code context like functions and classes at the top of editor when scroll"
|
||||
)]
|
||||
pub sticky_header: bool,
|
||||
#[field_names(desc = "The number of pixels to show completion")]
|
||||
pub completion_width: usize,
|
||||
#[field_names(
|
||||
desc = "If the editor should show the documentation of the current completion item"
|
||||
)]
|
||||
pub completion_show_documentation: bool,
|
||||
#[field_names(
|
||||
desc = "Should the completion item use the `detail` field to replace the label `field`?"
|
||||
)]
|
||||
pub completion_item_show_detail: bool,
|
||||
#[field_names(
|
||||
desc = "If the editor should show the signature of the function as the parameters are being typed"
|
||||
)]
|
||||
@@ -150,6 +159,11 @@ pub struct EditorConfig {
|
||||
pub inlay_hint_font_size: usize,
|
||||
#[field_names(desc = "If diagnostics should be displayed inline")]
|
||||
pub enable_error_lens: bool,
|
||||
|
||||
#[field_names(
|
||||
desc = "Only render the styling without displaying messages, provided that `Enable ErrorLens` is enabled"
|
||||
)]
|
||||
pub only_render_error_styling: bool,
|
||||
#[field_names(
|
||||
desc = "Whether error lens should go to the end of view line, or only to the end of the diagnostic"
|
||||
)]
|
||||
@@ -234,7 +248,7 @@ pub struct EditorConfig {
|
||||
|
||||
impl EditorConfig {
|
||||
pub fn font_size(&self) -> usize {
|
||||
self.font_size.max(6).min(32)
|
||||
self.font_size.clamp(6, 32)
|
||||
}
|
||||
|
||||
pub fn line_height(&self) -> usize {
|
||||
|
||||
@@ -38,6 +38,7 @@ impl LapceIcons {
|
||||
pub const KEYBOARD: &'static str = "keyboard";
|
||||
pub const BREADCRUMB_SEPARATOR: &'static str = "breadcrumb_separator";
|
||||
pub const SYMBOL_COLOR: &'static str = "symbol_color";
|
||||
pub const TYPE_HIERARCHY: &'static str = "type_hierarchy";
|
||||
|
||||
pub const FILE: &'static str = "file";
|
||||
pub const FILE_EXPLORER: &'static str = "file_explorer";
|
||||
@@ -108,6 +109,12 @@ impl LapceIcons {
|
||||
pub const FILE_TYPE_SYMLINK_FILE: &'static str = "file-symlink-file";
|
||||
pub const FILE_TYPE_SYMLINK_DIRECTORY: &'static str = "file-symlink-directory";
|
||||
|
||||
pub const DOCUMENT_SYMBOL: &'static str = "document_symbol";
|
||||
|
||||
pub const REFERENCES: &'static str = "references";
|
||||
|
||||
pub const IMPLEMENTATION: &'static str = "implementation";
|
||||
|
||||
pub const SYMBOL_KIND_ARRAY: &'static str = "symbol_kind.array";
|
||||
pub const SYMBOL_KIND_BOOLEAN: &'static str = "symbol_kind.boolean";
|
||||
pub const SYMBOL_KIND_CLASS: &'static str = "symbol_kind.class";
|
||||
|
||||
@@ -58,9 +58,8 @@ impl IconThemeConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::config::icon_theme::try_all_equal_value;
|
||||
|
||||
use super::IconThemeConfig;
|
||||
use crate::config::icon_theme::try_all_equal_value;
|
||||
|
||||
#[test]
|
||||
fn try_all_equal_value_empty_none() {
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use include_dir::{include_dir, Dir};
|
||||
use include_dir::{Dir, include_dir};
|
||||
|
||||
use crate::config::LOGO;
|
||||
|
||||
const CODICONS_ICONS_DIR: Dir = include_dir!("../icons/codicons");
|
||||
const LAPCE_ICONS_DIR: Dir = include_dir!("../icons/lapce");
|
||||
const CODICONS_ICONS_DIR: Dir =
|
||||
include_dir!("$CARGO_MANIFEST_DIR/../icons/codicons");
|
||||
const LAPCE_ICONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/../icons/lapce");
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SvgStore {
|
||||
|
||||
@@ -19,9 +19,10 @@ pub struct TerminalConfig {
|
||||
desc = "Set the terminal line height, If 0, it uses editor line height"
|
||||
)]
|
||||
pub line_height: f64,
|
||||
#[field_names(desc = "Profiles available in terminal pane")]
|
||||
|
||||
#[field_names(skip)]
|
||||
pub profiles: HashMap<String, TerminalProfile>,
|
||||
#[field_names(desc = "Default profile for each platform")]
|
||||
#[field_names(skip)]
|
||||
pub default_profile: HashMap<String, String>,
|
||||
|
||||
#[serde(skip)]
|
||||
@@ -51,7 +52,7 @@ impl TerminalConfig {
|
||||
for b in 0..6 {
|
||||
// Override colors 16..232 with the config (if present).
|
||||
let index = 16 + r * 36 + g * 6 + b;
|
||||
let color = Color::rgb8(
|
||||
let color = Color::from_rgb8(
|
||||
if r == 0 { 0 } else { r * 40 + 55 },
|
||||
if g == 0 { 0 } else { g * 40 + 55 },
|
||||
if b == 0 { 0 } else { b * 40 + 55 },
|
||||
@@ -67,7 +68,7 @@ impl TerminalConfig {
|
||||
// Override colors 232..256 with the config (if present).
|
||||
|
||||
let value = i * 10 + 8;
|
||||
indexed_colors.insert(index + i, Color::rgb8(value, value, value));
|
||||
indexed_colors.insert(index + i, Color::from_rgb8(value, value, value));
|
||||
}
|
||||
|
||||
self.indexed_colors = Arc::new(indexed_colors);
|
||||
@@ -78,7 +79,7 @@ impl TerminalConfig {
|
||||
) -> Option<lapce_rpc::terminal::TerminalProfile> {
|
||||
let profile = self.profiles.get(
|
||||
self.default_profile
|
||||
.get(&std::env::consts::OS.to_string())
|
||||
.get(std::env::consts::OS)
|
||||
.unwrap_or(&String::from("default")),
|
||||
)?;
|
||||
let workdir = if let Some(workdir) = &profile.workdir {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use floem::cosmic_text::FamilyOwned;
|
||||
use floem::text::FamilyOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use structdesc::FieldNames;
|
||||
|
||||
#[derive(FieldNames, Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct UIConfig {
|
||||
#[field_names(desc = "Set the UI scale. Defaults to 1.0")]
|
||||
scale: f64,
|
||||
|
||||
#[field_names(
|
||||
desc = "Set the UI font family. If empty, it uses system default."
|
||||
)]
|
||||
@@ -27,14 +30,19 @@ pub struct UIConfig {
|
||||
#[field_names(desc = "Set the minimum width for editor tab")]
|
||||
tab_min_width: usize,
|
||||
|
||||
#[field_names(
|
||||
desc = "Set whether the editor tab separator should be full height or the height of the content"
|
||||
)]
|
||||
pub tab_separator_height: TabSeparatorHeight,
|
||||
|
||||
#[field_names(desc = "Set the width for scroll bar")]
|
||||
scroll_width: usize,
|
||||
|
||||
#[field_names(desc = "Controls the width of drop shadow in the UI")]
|
||||
drop_shadow_width: usize,
|
||||
|
||||
#[field_names(desc = "Controls the width of the preview editor")]
|
||||
pub preview_editor_width: usize,
|
||||
#[field_names(desc = "Controls the width of the command palette")]
|
||||
palette_width: usize,
|
||||
|
||||
#[field_names(
|
||||
desc = "Set the hover font family. If empty, it uses the UI font family"
|
||||
@@ -76,9 +84,32 @@ pub enum TabCloseButton {
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
strum_macros::VariantNames,
|
||||
)]
|
||||
pub enum TabSeparatorHeight {
|
||||
#[default]
|
||||
Content,
|
||||
Full,
|
||||
}
|
||||
|
||||
impl UIConfig {
|
||||
pub fn scale(&self) -> f64 {
|
||||
self.scale.clamp(0.1, 4.0)
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> usize {
|
||||
self.font_size.max(6).min(32)
|
||||
self.font_size.clamp(6, 32)
|
||||
}
|
||||
|
||||
pub fn font_family(&self) -> Vec<FamilyOwned> {
|
||||
@@ -94,7 +125,7 @@ impl UIConfig {
|
||||
if self.icon_size == 0 {
|
||||
self.font_size()
|
||||
} else {
|
||||
self.icon_size.max(6).min(32)
|
||||
self.icon_size.clamp(6, 32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +133,12 @@ impl UIConfig {
|
||||
let font_size = self.font_size();
|
||||
self.status_height.max(font_size)
|
||||
}
|
||||
|
||||
pub fn palette_width(&self) -> usize {
|
||||
if self.palette_width == 0 {
|
||||
500
|
||||
} else {
|
||||
self.palette_width.max(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use std::sync::{Arc, atomic::AtomicBool, mpsc::Sender};
|
||||
|
||||
pub struct ConfigWatcher {
|
||||
tx: Sender<()>,
|
||||
@@ -9,8 +7,8 @@ pub struct ConfigWatcher {
|
||||
|
||||
impl notify::EventHandler for ConfigWatcher {
|
||||
fn handle_event(&mut self, event: notify::Result<notify::Event>) {
|
||||
if let Ok(event) = event {
|
||||
match event.kind {
|
||||
match event {
|
||||
Ok(event) => match event.kind {
|
||||
notify::EventKind::Create(_)
|
||||
| notify::EventKind::Modify(_)
|
||||
| notify::EventKind::Remove(_) => {
|
||||
@@ -30,13 +28,18 @@ impl notify::EventHandler for ConfigWatcher {
|
||||
std::thread::sleep(std::time::Duration::from_millis(
|
||||
500,
|
||||
));
|
||||
let _ = tx.send(());
|
||||
if let Err(err) = tx.send(()) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
config_mutex
|
||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+99
-50
@@ -4,16 +4,17 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossbeam_channel::{unbounded, Sender};
|
||||
use floem::peniko::kurbo::Vec2;
|
||||
use anyhow::{Result, anyhow};
|
||||
use crossbeam_channel::{Sender, unbounded};
|
||||
use floem::{peniko::kurbo::Vec2, reactive::SignalGet};
|
||||
use lapce_core::directory::Directory;
|
||||
use lapce_rpc::plugin::VoltID;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{
|
||||
app::{AppData, AppInfo},
|
||||
doc::DocInfo,
|
||||
panel::{data::PanelOrder, kind::PanelKind, position::PanelPosition},
|
||||
panel::{data::PanelOrder, kind::PanelKind},
|
||||
window::{WindowData, WindowInfo},
|
||||
window_tab::WindowTabData,
|
||||
workspace::{LapceWorkspace, WorkspaceInfo},
|
||||
@@ -50,7 +51,9 @@ impl LapceDb {
|
||||
.ok_or_else(|| anyhow!("can't get config directory"))?
|
||||
.join("db");
|
||||
let workspace_folder = folder.join("workspaces");
|
||||
let _ = std::fs::create_dir_all(&workspace_folder);
|
||||
if let Err(err) = std::fs::create_dir_all(&workspace_folder) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
|
||||
let (save_tx, save_rx) = unbounded();
|
||||
|
||||
@@ -60,35 +63,57 @@ impl LapceDb {
|
||||
folder,
|
||||
};
|
||||
let local_db = db.clone();
|
||||
std::thread::spawn(move || -> Result<()> {
|
||||
loop {
|
||||
let event = save_rx.recv()?;
|
||||
match event {
|
||||
SaveEvent::App(info) => {
|
||||
let _ = local_db.insert_app_info(info);
|
||||
}
|
||||
SaveEvent::Workspace(workspace, info) => {
|
||||
let _ = local_db.insert_workspace(&workspace, &info);
|
||||
}
|
||||
SaveEvent::RecentWorkspace(workspace) => {
|
||||
let _ = local_db.insert_recent_workspace(workspace);
|
||||
}
|
||||
SaveEvent::Doc(info) => {
|
||||
let _ = local_db.insert_doc(&info);
|
||||
}
|
||||
SaveEvent::DisabledVolts(volts) => {
|
||||
let _ = local_db.insert_disabled_volts(volts);
|
||||
}
|
||||
SaveEvent::WorkspaceDisabledVolts(workspace, volts) => {
|
||||
let _ = local_db
|
||||
.insert_workspace_disabled_volts(workspace, volts);
|
||||
}
|
||||
SaveEvent::PanelOrder(order) => {
|
||||
let _ = local_db.insert_panel_orders(&order);
|
||||
std::thread::Builder::new()
|
||||
.name("SaveEventHandler".to_owned())
|
||||
.spawn(move || -> Result<()> {
|
||||
loop {
|
||||
let event = save_rx.recv()?;
|
||||
match event {
|
||||
SaveEvent::App(info) => {
|
||||
if let Err(err) = local_db.insert_app_info(info) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::Workspace(workspace, info) => {
|
||||
if let Err(err) =
|
||||
local_db.insert_workspace(&workspace, &info)
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::RecentWorkspace(workspace) => {
|
||||
if let Err(err) =
|
||||
local_db.insert_recent_workspace(workspace)
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::Doc(info) => {
|
||||
if let Err(err) = local_db.insert_doc(&info) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::DisabledVolts(volts) => {
|
||||
if let Err(err) = local_db.insert_disabled_volts(volts) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::WorkspaceDisabledVolts(workspace, volts) => {
|
||||
if let Err(err) = local_db
|
||||
.insert_workspace_disabled_volts(workspace, volts)
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
SaveEvent::PanelOrder(order) => {
|
||||
if let Err(err) = local_db.insert_panel_orders(&order) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
@@ -99,7 +124,9 @@ impl LapceDb {
|
||||
}
|
||||
|
||||
pub fn save_disabled_volts(&self, volts: Vec<VoltID>) {
|
||||
let _ = self.save_tx.send(SaveEvent::DisabledVolts(volts));
|
||||
if let Err(err) = self.save_tx.send(SaveEvent::DisabledVolts(volts)) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_workspace_disabled_volts(
|
||||
@@ -107,9 +134,12 @@ impl LapceDb {
|
||||
workspace: Arc<LapceWorkspace>,
|
||||
volts: Vec<VoltID>,
|
||||
) {
|
||||
let _ = self
|
||||
if let Err(err) = self
|
||||
.save_tx
|
||||
.send(SaveEvent::WorkspaceDisabledVolts(workspace, volts));
|
||||
.send(SaveEvent::WorkspaceDisabledVolts(workspace, volts))
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_disabled_volts(&self, volts: Vec<VoltID>) -> Result<()> {
|
||||
@@ -126,7 +156,9 @@ impl LapceDb {
|
||||
let folder = self
|
||||
.workspace_folder
|
||||
.join(workspace_folder_name(&workspace));
|
||||
let _ = std::fs::create_dir_all(&folder);
|
||||
if let Err(err) = std::fs::create_dir_all(&folder) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
|
||||
let volts = serde_json::to_string_pretty(&volts)?;
|
||||
std::fs::write(folder.join(DISABLED_VOLTS), volts)?;
|
||||
@@ -218,7 +250,9 @@ impl LapceDb {
|
||||
info: &WorkspaceInfo,
|
||||
) -> Result<()> {
|
||||
let folder = self.workspace_folder.join(workspace_folder_name(workspace));
|
||||
let _ = std::fs::create_dir_all(&folder);
|
||||
if let Err(err) = std::fs::create_dir_all(&folder) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
let workspace_info = serde_json::to_string_pretty(info)?;
|
||||
std::fs::write(folder.join(WORKSPACE_INFO), workspace_info)?;
|
||||
Ok(())
|
||||
@@ -227,7 +261,9 @@ impl LapceDb {
|
||||
pub fn save_app(&self, data: &AppData) -> Result<()> {
|
||||
let windows = data.windows.get_untracked();
|
||||
for (_, window) in &windows {
|
||||
let _ = self.save_window(window.clone());
|
||||
if let Err(err) = self.save_window(window.clone()) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let info = AppInfo {
|
||||
@@ -258,7 +294,9 @@ impl LapceDb {
|
||||
return Ok(());
|
||||
}
|
||||
for (_, window) in &windows {
|
||||
let _ = self.insert_window(window.clone());
|
||||
if let Err(err) = self.insert_window(window.clone()) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
let info = AppInfo {
|
||||
windows: windows
|
||||
@@ -278,7 +316,7 @@ impl LapceDb {
|
||||
window.size.width = 800.0;
|
||||
}
|
||||
if window.size.height < 10.0 {
|
||||
window.size.width = 600.0;
|
||||
window.size.height = 600.0;
|
||||
}
|
||||
}
|
||||
Ok(info)
|
||||
@@ -291,21 +329,25 @@ impl LapceDb {
|
||||
info.size.width = 800.0;
|
||||
}
|
||||
if info.size.height < 10.0 {
|
||||
info.size.width = 600.0;
|
||||
info.size.height = 600.0;
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn save_window(&self, data: WindowData) -> Result<()> {
|
||||
for (_, window_tab) in data.window_tabs.get_untracked().into_iter() {
|
||||
let _ = self.save_window_tab(window_tab);
|
||||
if let Err(err) = self.save_window_tab(window_tab) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_window(&self, data: WindowData) -> Result<()> {
|
||||
for (_, window_tab) in data.window_tabs.get_untracked().into_iter() {
|
||||
let _ = self.insert_window_tab(window_tab);
|
||||
if let Err(err) = self.insert_window_tab(window_tab) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
let info = data.info();
|
||||
let info = serde_json::to_string_pretty(&info)?;
|
||||
@@ -330,7 +372,8 @@ impl LapceDb {
|
||||
use strum::IntoEnumIterator;
|
||||
for kind in PanelKind::iter() {
|
||||
if kind.position(&panel_orders).is_none() {
|
||||
let panels = panel_orders.entry(PanelPosition::LeftTop).or_default();
|
||||
let panels =
|
||||
panel_orders.entry(kind.default_position()).or_default();
|
||||
panels.push_back(kind);
|
||||
}
|
||||
}
|
||||
@@ -339,7 +382,9 @@ impl LapceDb {
|
||||
}
|
||||
|
||||
pub fn save_panel_orders(&self, order: PanelOrder) {
|
||||
let _ = self.save_tx.send(SaveEvent::PanelOrder(order));
|
||||
if let Err(err) = self.save_tx.send(SaveEvent::PanelOrder(order)) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_panel_orders(&self, order: &PanelOrder) -> Result<()> {
|
||||
@@ -361,7 +406,9 @@ impl LapceDb {
|
||||
scroll_offset: (scroll_offset.x, scroll_offset.y),
|
||||
cursor_offset,
|
||||
};
|
||||
let _ = self.save_tx.send(SaveEvent::Doc(info));
|
||||
if let Err(err) = self.save_tx.send(SaveEvent::Doc(info)) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_doc(&self, info: &DocInfo) -> Result<()> {
|
||||
@@ -369,7 +416,9 @@ impl LapceDb {
|
||||
.workspace_folder
|
||||
.join(workspace_folder_name(&info.workspace))
|
||||
.join(WORKSPACE_FILES);
|
||||
let _ = std::fs::create_dir_all(&folder);
|
||||
if let Err(err) = std::fs::create_dir_all(&folder) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
let contents = serde_json::to_string_pretty(info)?;
|
||||
std::fs::write(folder.join(doc_path_name(&info.path)), contents)?;
|
||||
Ok(())
|
||||
@@ -397,7 +446,7 @@ fn workspace_folder_name(workspace: &LapceWorkspace) -> String {
|
||||
}
|
||||
|
||||
fn doc_path_name(path: &Path) -> String {
|
||||
url::form_urlencoded::Serializer::new(String::new())
|
||||
.append_key_only(&path.to_string_lossy())
|
||||
.finish()
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(path.to_string_lossy().as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
|
||||
use floem::{
|
||||
ext_event::create_ext_action,
|
||||
reactive::{Memo, RwSignal, Scope},
|
||||
reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
views::VirtualVector,
|
||||
};
|
||||
use lapce_rpc::{
|
||||
@@ -361,7 +361,7 @@ impl DapData {
|
||||
let variables_id = self.variables_id;
|
||||
|
||||
let send = create_ext_action(self.common.scope, move |result| {
|
||||
if let Ok(ProxyResponse::DapVariableResponse { varialbes }) = result {
|
||||
if let Ok(ProxyResponse::DapVariableResponse { variables }) = result {
|
||||
variables_id.update(|id| {
|
||||
*id += 1;
|
||||
});
|
||||
@@ -370,7 +370,7 @@ impl DapData {
|
||||
let mut new_parent = parent.clone();
|
||||
new_parent.push(reference);
|
||||
var.read = true;
|
||||
var.children = varialbes
|
||||
var.children = variables
|
||||
.into_iter()
|
||||
.map(|v| DapVariable {
|
||||
item: ScopeOrVar::Var(v),
|
||||
@@ -521,7 +521,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_update_count() {
|
||||
let variables = vec![
|
||||
let variables = [
|
||||
(
|
||||
Scope {
|
||||
variables_reference: 0,
|
||||
@@ -610,7 +610,7 @@ mod tests {
|
||||
let var = root.get_var_mut(&[0], 3).unwrap();
|
||||
var.expanded = true;
|
||||
var.read = true;
|
||||
var.children = vec![
|
||||
var.children = [
|
||||
Variable {
|
||||
variables_reference: 9,
|
||||
..Default::default()
|
||||
|
||||
+364
-109
@@ -5,18 +5,25 @@ use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{atomic, Arc},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{self, AtomicUsize},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use floem::{
|
||||
ViewId,
|
||||
action::exec_after,
|
||||
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout},
|
||||
ext_event::create_ext_action,
|
||||
keyboard::Modifiers,
|
||||
peniko::Color,
|
||||
reactive::{batch, ReadSignal, RwSignal, Scope},
|
||||
reactive::{
|
||||
ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, batch,
|
||||
},
|
||||
text::{Attrs, AttrsList, FamilyOwned, TextLayout},
|
||||
views::editor::{
|
||||
CursorInfo, Editor, EditorStyle,
|
||||
actions::CommonAction,
|
||||
command::{Command, CommandExecuted},
|
||||
id::EditorId,
|
||||
@@ -24,19 +31,18 @@ use floem::{
|
||||
phantom_text::{PhantomText, PhantomTextKind, PhantomTextLine},
|
||||
text::{Document, DocumentPhantom, PreeditData, Styling, SystemClipboard},
|
||||
view::{ScreenLines, ScreenLinesBase},
|
||||
CursorInfo, Editor, EditorStyle,
|
||||
},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use lapce_core::{
|
||||
buffer::{
|
||||
diff::{rope_diff, DiffLines},
|
||||
rope_text::RopeText,
|
||||
Buffer, InvalLines,
|
||||
diff::{DiffLines, rope_diff},
|
||||
rope_text::RopeText,
|
||||
},
|
||||
char_buffer::CharBuffer,
|
||||
command::EditCommand,
|
||||
cursor::Cursor,
|
||||
cursor::{Cursor, CursorAffinity},
|
||||
editor::{Action, EditConf, EditType},
|
||||
indent::IndentStyle,
|
||||
language::LapceLanguage,
|
||||
@@ -46,8 +52,8 @@ use lapce_core::{
|
||||
rope_text_pos::RopeTextPosition,
|
||||
selection::{InsertDrift, Selection},
|
||||
style::line_styles,
|
||||
syntax::{edit::SyntaxEdit, BracketParser, Syntax},
|
||||
word::WordCursor,
|
||||
syntax::{BracketParser, Syntax, edit::SyntaxEdit},
|
||||
word::{CharClassification, WordCursor, get_char_property},
|
||||
};
|
||||
use lapce_rpc::{
|
||||
buffer::BufferId,
|
||||
@@ -56,24 +62,28 @@ use lapce_rpc::{
|
||||
style::{LineStyle, LineStyles, Style},
|
||||
};
|
||||
use lapce_xi_rope::{
|
||||
spans::{Spans, SpansBuilder},
|
||||
Interval, Rope, RopeDelta, Transformer,
|
||||
spans::{Spans, SpansBuilder},
|
||||
};
|
||||
use lsp_types::{
|
||||
CodeActionResponse, Diagnostic, DiagnosticSeverity, InlayHint, InlayHintLabel,
|
||||
CodeActionOrCommand, CodeLens, Diagnostic, DiagnosticSeverity,
|
||||
DocumentSymbolResponse, InlayHint, InlayHintLabel, TextEdit,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
command::{CommandKind, LapceCommand},
|
||||
config::{color::LapceColor, LapceConfig},
|
||||
editor::{compute_screen_lines, EditorData},
|
||||
config::{LapceConfig, color::LapceColor},
|
||||
editor::{EditorData, compute_screen_lines, gutter::FoldingRanges},
|
||||
find::{Find, FindProgress, FindResult},
|
||||
history::DocumentHistory,
|
||||
keypress::KeyPressFocus,
|
||||
main_split::Editors,
|
||||
panel::kind::PanelKind,
|
||||
panel::{
|
||||
document_symbol::{SymbolData, SymbolInformationItemData},
|
||||
kind::PanelKind,
|
||||
},
|
||||
window_tab::{CommonData, Focus},
|
||||
workspace::LapceWorkspace,
|
||||
};
|
||||
@@ -101,7 +111,7 @@ pub struct DocHistory {
|
||||
pub enum DocContent {
|
||||
/// A file at some location. This can be a remote path.
|
||||
File { path: PathBuf, read_only: bool },
|
||||
/// A local document, which doens't need to be sync to the disk.
|
||||
/// A local document, which doesn't need to be sync to the disk.
|
||||
Local,
|
||||
/// A document of an old version in the source control
|
||||
History(DocHistory),
|
||||
@@ -146,7 +156,10 @@ pub struct DocInfo {
|
||||
}
|
||||
|
||||
/// (Offset -> (Plugin the code actions are from, Code Actions))
|
||||
pub type CodeActions = im::HashMap<usize, Arc<(PluginId, CodeActionResponse)>>;
|
||||
pub type CodeActions =
|
||||
im::HashMap<usize, (PluginId, im::Vector<CodeActionOrCommand>)>;
|
||||
|
||||
pub type AllCodeLens = im::HashMap<usize, (PluginId, usize, im::Vector<CodeLens>)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Doc {
|
||||
@@ -167,7 +180,7 @@ pub struct Doc {
|
||||
/// (line, col)
|
||||
pub completion_pos: RwSignal<(usize, usize)>,
|
||||
|
||||
/// Current inline completion text, if any.
|
||||
/// Current inline completion text, if any.
|
||||
/// This will be displayed even on views that are not focused.
|
||||
pub inline_completion: RwSignal<Option<String>>,
|
||||
/// (line, col)
|
||||
@@ -176,6 +189,10 @@ pub struct Doc {
|
||||
/// (Offset -> (Plugin the code actions are from, Code Actions))
|
||||
pub code_actions: RwSignal<CodeActions>,
|
||||
|
||||
pub code_lens: RwSignal<AllCodeLens>,
|
||||
|
||||
pub folding_ranges: RwSignal<FoldingRanges>,
|
||||
|
||||
/// Stores information about different versions of the document from source control.
|
||||
histories: RwSignal<im::HashMap<String, DocumentHistory>>,
|
||||
pub head_changes: RwSignal<im::Vector<DiffLines>>,
|
||||
@@ -195,6 +212,8 @@ pub struct Doc {
|
||||
|
||||
editors: Editors,
|
||||
pub common: Rc<CommonData>,
|
||||
|
||||
pub document_symbol_data: RwSignal<Option<SymbolData>>,
|
||||
}
|
||||
impl Doc {
|
||||
pub fn new(
|
||||
@@ -238,6 +257,9 @@ impl Doc {
|
||||
preedit: PreeditData::new(cx),
|
||||
editors,
|
||||
common,
|
||||
code_lens: cx.create_rw_signal(im::HashMap::new()),
|
||||
document_symbol_data: cx.create_rw_signal(None),
|
||||
folding_ranges: cx.create_rw_signal(FoldingRanges::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +308,9 @@ impl Doc {
|
||||
preedit: PreeditData::new(cx),
|
||||
editors,
|
||||
common,
|
||||
code_lens: cx.create_rw_signal(im::HashMap::new()),
|
||||
document_symbol_data: cx.create_rw_signal(None),
|
||||
folding_ranges: cx.create_rw_signal(FoldingRanges::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +359,9 @@ impl Doc {
|
||||
preedit: PreeditData::new(cx),
|
||||
editors,
|
||||
common,
|
||||
code_lens: cx.create_rw_signal(im::HashMap::new()),
|
||||
document_symbol_data: cx.create_rw_signal(None),
|
||||
folding_ranges: cx.create_rw_signal(FoldingRanges::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +464,7 @@ impl Doc {
|
||||
self.parser.borrow_mut().update_code(
|
||||
code,
|
||||
&self.buffer.get_untracked(),
|
||||
Some(syntax.clone()),
|
||||
Some(syntax),
|
||||
);
|
||||
} else {
|
||||
self.parser.borrow_mut().update_code(
|
||||
@@ -561,9 +589,9 @@ impl Doc {
|
||||
buffer.set_cursor_before(old_cursor);
|
||||
buffer.set_cursor_after(cursor.mode.clone());
|
||||
});
|
||||
self.apply_deltas(&deltas);
|
||||
}
|
||||
|
||||
self.apply_deltas(&deltas);
|
||||
deltas
|
||||
}
|
||||
|
||||
@@ -596,7 +624,7 @@ impl Doc {
|
||||
SyntaxEdit::from_delta(before_text, delta.clone())
|
||||
})
|
||||
.collect();
|
||||
self.on_update(Some(edits.as_slice()));
|
||||
self.on_update(Some(edits));
|
||||
}
|
||||
|
||||
pub fn is_pristine(&self) -> bool {
|
||||
@@ -608,17 +636,16 @@ impl Doc {
|
||||
self.buffer.with_untracked(|b| b.rev())
|
||||
}
|
||||
|
||||
/// Get the buffer's line-ending.
|
||||
/// Get the buffer's line-ending.
|
||||
/// Note: this may not be the same as what the actual line endings in the file are, rather this
|
||||
/// is what the line-ending is set to (and what it will be saved as).
|
||||
pub fn line_ending(&self) -> LineEnding {
|
||||
self.buffer.with_untracked(|b| b.line_ending())
|
||||
}
|
||||
|
||||
fn on_update(&self, edits: Option<&[SyntaxEdit]>) {
|
||||
fn on_update(&self, edits: Option<SmallVec<[SyntaxEdit; 3]>>) {
|
||||
batch(|| {
|
||||
self.trigger_syntax_change(edits);
|
||||
self.clear_sticky_headers_cache();
|
||||
self.trigger_head_change();
|
||||
self.check_auto_save();
|
||||
self.get_inlay_hints();
|
||||
@@ -627,35 +654,60 @@ impl Doc {
|
||||
self.do_bracket_colorization();
|
||||
self.clear_code_actions();
|
||||
self.clear_style_cache();
|
||||
self.get_code_lens();
|
||||
self.get_document_symbol();
|
||||
self.get_folding_range();
|
||||
});
|
||||
}
|
||||
|
||||
fn do_bracket_colorization(&self) {
|
||||
self.syntax.with_untracked(|syntax| {
|
||||
if syntax.styles.is_some() {
|
||||
self.parser.borrow_mut().update_code(
|
||||
self.buffer.get_untracked().to_string(),
|
||||
&self.buffer.get_untracked(),
|
||||
Some(syntax.clone()),
|
||||
);
|
||||
} else {
|
||||
self.parser.borrow_mut().update_code(
|
||||
self.buffer.get_untracked().to_string(),
|
||||
&self.buffer.get_untracked(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
})
|
||||
if self.parser.borrow().active {
|
||||
self.syntax.with_untracked(|syntax| {
|
||||
if syntax.rev == self.rev() && syntax.styles.is_some() {
|
||||
self.parser.borrow_mut().update_code(
|
||||
self.buffer.get_untracked().to_string(),
|
||||
&self.buffer.get_untracked(),
|
||||
Some(syntax),
|
||||
);
|
||||
} else {
|
||||
self.parser.borrow_mut().update_code(
|
||||
self.buffer.get_untracked().to_string(),
|
||||
&self.buffer.get_untracked(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_text_edit(&self, edits: &[TextEdit]) {
|
||||
let edits = self.buffer.with_untracked(|buffer| {
|
||||
edits
|
||||
.iter()
|
||||
.map(|edit| {
|
||||
let selection = lapce_core::selection::Selection::region(
|
||||
buffer.offset_of_position(&edit.range.start),
|
||||
buffer.offset_of_position(&edit.range.end),
|
||||
);
|
||||
(selection, edit.new_text.as_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
self.do_raw_edit(&edits, EditType::Completion);
|
||||
}
|
||||
|
||||
fn check_auto_save(&self) {
|
||||
let config = self.common.config.get_untracked();
|
||||
if config.editor.autosave_interval > 0 {
|
||||
if !self.content.with_untracked(|c| c.is_file()) {
|
||||
let Some(path) = self.content.with_untracked(|c| c.path().cloned())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let rev = self.rev();
|
||||
let doc = self.clone();
|
||||
let scope = self.scope;
|
||||
let proxy = self.common.proxy.clone();
|
||||
let format = config.editor.format_on_save;
|
||||
exec_after(
|
||||
Duration::from_millis(config.editor.autosave_interval),
|
||||
move |_| {
|
||||
@@ -671,7 +723,26 @@ impl Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
doc.save(|| {});
|
||||
if format {
|
||||
let send = create_ext_action(scope, move |result| {
|
||||
let current_rev = doc.rev();
|
||||
if current_rev != rev {
|
||||
return;
|
||||
}
|
||||
if let Ok(ProxyResponse::GetDocumentFormatting {
|
||||
edits,
|
||||
}) = result
|
||||
{
|
||||
doc.do_text_edit(&edits);
|
||||
}
|
||||
doc.save(|| {});
|
||||
});
|
||||
proxy.get_document_formatting(path, move |result| {
|
||||
send(result);
|
||||
});
|
||||
} else {
|
||||
doc.save(|| {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -704,12 +775,28 @@ impl Doc {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn trigger_syntax_change(&self, edits: Option<&[SyntaxEdit]>) {
|
||||
pub fn trigger_syntax_change(&self, edits: Option<SmallVec<[SyntaxEdit; 3]>>) {
|
||||
let (rev, text) =
|
||||
self.buffer.with_untracked(|b| (b.rev(), b.text().clone()));
|
||||
|
||||
let doc = self.clone();
|
||||
let send = create_ext_action(self.scope, move |syntax| {
|
||||
if doc.buffer.with_untracked(|b| b.rev()) == rev {
|
||||
doc.syntax.set(syntax);
|
||||
doc.do_bracket_colorization();
|
||||
doc.clear_style_cache();
|
||||
doc.clear_sticky_headers_cache();
|
||||
}
|
||||
});
|
||||
|
||||
self.syntax.update(|syntax| {
|
||||
syntax.parse(rev, text, edits);
|
||||
syntax.cancel_flag.store(1, atomic::Ordering::Relaxed);
|
||||
syntax.cancel_flag = Arc::new(AtomicUsize::new(0));
|
||||
});
|
||||
let mut syntax = self.syntax.get_untracked();
|
||||
rayon::spawn(move || {
|
||||
syntax.parse(rev, text, edits.as_deref());
|
||||
send(syntax);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -771,7 +858,7 @@ impl Doc {
|
||||
}
|
||||
|
||||
/// Request semantic styles for the buffer from the LSP through the proxy.
|
||||
fn get_semantic_styles(&self) {
|
||||
pub fn get_semantic_styles(&self) {
|
||||
if !self.loaded() {
|
||||
return;
|
||||
}
|
||||
@@ -783,24 +870,37 @@ impl Doc {
|
||||
return;
|
||||
};
|
||||
|
||||
let (rev, len) = self.buffer.with_untracked(|b| (b.rev(), b.len()));
|
||||
|
||||
let syntactic_styles =
|
||||
self.syntax.with_untracked(|syntax| syntax.styles.clone());
|
||||
let (atomic_rev, rev, len) = self
|
||||
.buffer
|
||||
.with_untracked(|b| (b.atomic_rev(), b.rev(), b.len()));
|
||||
|
||||
let doc = self.clone();
|
||||
let send = create_ext_action(self.scope, move |styles| {
|
||||
if doc.buffer.with_untracked(|b| b.rev()) == rev {
|
||||
doc.semantic_styles.set(Some(styles));
|
||||
doc.clear_style_cache();
|
||||
if let Some(styles) = styles {
|
||||
if doc.buffer.with_untracked(|b| b.rev()) == rev {
|
||||
doc.semantic_styles.set(Some(styles));
|
||||
doc.clear_style_cache();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.common.proxy.get_semantic_tokens(path, move |result| {
|
||||
if let Ok(ProxyResponse::GetSemanticTokens { styles }) = result {
|
||||
rayon::spawn(move || {
|
||||
if styles.styles.is_empty() {
|
||||
send(None);
|
||||
return;
|
||||
}
|
||||
if atomic_rev.load(atomic::Ordering::Acquire) != rev {
|
||||
send(None);
|
||||
return;
|
||||
}
|
||||
std::thread::spawn(move || {
|
||||
let mut styles_span = SpansBuilder::new(len);
|
||||
for style in styles.styles {
|
||||
if atomic_rev.load(atomic::Ordering::Acquire) != rev {
|
||||
send(None);
|
||||
return;
|
||||
}
|
||||
styles_span.add_span(
|
||||
Interval::new(style.start, style.end),
|
||||
style.style,
|
||||
@@ -808,26 +908,103 @@ impl Doc {
|
||||
}
|
||||
|
||||
let styles = styles_span.build();
|
||||
|
||||
let styles = if let Some(syntactic_styles) = syntactic_styles {
|
||||
syntactic_styles.merge(&styles, |a, b| {
|
||||
if let Some(b) = b {
|
||||
return b.clone();
|
||||
}
|
||||
a.clone()
|
||||
})
|
||||
} else {
|
||||
styles
|
||||
};
|
||||
|
||||
send(styles);
|
||||
send(Some(styles));
|
||||
});
|
||||
} else {
|
||||
send(None);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_code_lens(&self) {
|
||||
let cx = self.scope;
|
||||
let doc = self.clone();
|
||||
self.code_lens.update(|code_lens| {
|
||||
code_lens.clear();
|
||||
});
|
||||
let rev = self.rev();
|
||||
if let DocContent::File { path, .. } = doc.content.get_untracked() {
|
||||
let send = create_ext_action(cx, move |result| {
|
||||
if rev != doc.rev() {
|
||||
return;
|
||||
}
|
||||
if let Ok(ProxyResponse::GetCodeLensResponse { plugin_id, resp }) =
|
||||
result
|
||||
{
|
||||
let Some(codelens) = resp else {
|
||||
return;
|
||||
};
|
||||
doc.code_lens.update(|code_lens| {
|
||||
for codelens in codelens {
|
||||
if codelens.command.is_none() {
|
||||
continue;
|
||||
}
|
||||
let entry = code_lens
|
||||
.entry(codelens.range.start.line as usize)
|
||||
.or_insert_with(|| {
|
||||
(
|
||||
plugin_id,
|
||||
doc.buffer.with_untracked(|b| {
|
||||
b.offset_of_line(
|
||||
codelens.range.start.line as usize,
|
||||
)
|
||||
}),
|
||||
im::Vector::new(),
|
||||
)
|
||||
});
|
||||
entry.2.push_back(codelens);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
self.common.proxy.get_code_lens(path, move |result| {
|
||||
send(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_document_symbol(&self) {
|
||||
let cx = self.scope;
|
||||
let doc = self.clone();
|
||||
let rev = self.rev();
|
||||
if let DocContent::File { path, .. } = doc.content.get_untracked() {
|
||||
let send = create_ext_action(cx, {
|
||||
let path = path.clone();
|
||||
move |result| {
|
||||
if rev != doc.rev() {
|
||||
return;
|
||||
}
|
||||
if let Ok(ProxyResponse::GetDocumentSymbols { resp }) = result {
|
||||
let items: Vec<RwSignal<SymbolInformationItemData>> =
|
||||
match resp {
|
||||
DocumentSymbolResponse::Flat(_symbols) => {
|
||||
Vec::with_capacity(0)
|
||||
}
|
||||
DocumentSymbolResponse::Nested(symbols) => symbols
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
cx.create_rw_signal(
|
||||
SymbolInformationItemData::from((x, cx)),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let symbol_new = Some(SymbolData::new(items, path, cx));
|
||||
doc.document_symbol_data.update(|symbol| {
|
||||
*symbol = symbol_new;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.common.proxy.get_document_symbols(path, move |result| {
|
||||
send(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Request inlay hints for the buffer from the LSP through the proxy.
|
||||
fn get_inlay_hints(&self) {
|
||||
pub fn get_inlay_hints(&self) {
|
||||
if !self.loaded() {
|
||||
return;
|
||||
}
|
||||
@@ -911,6 +1088,43 @@ impl Doc {
|
||||
self.clear_code_actions();
|
||||
}
|
||||
|
||||
pub fn get_folding_range(&self) {
|
||||
// let cx = self.scope;
|
||||
// let doc = self.clone();
|
||||
// let rev = self.rev();
|
||||
// if let DocContent::File { path, .. } = doc.content.get_untracked() {
|
||||
// let send = create_ext_action(cx, {
|
||||
// move |result| {
|
||||
// if rev != doc.rev() {
|
||||
// return;
|
||||
// }
|
||||
// if let Ok(ProxyResponse::LspFoldingRangeResponse {
|
||||
// resp, ..
|
||||
// }) = result
|
||||
// {
|
||||
// let folding = resp
|
||||
// .unwrap_or_default()
|
||||
// .into_iter()
|
||||
// .map(|x| {
|
||||
// crate::editor::gutter::FoldingRange::from_lsp(x)
|
||||
// })
|
||||
// .sorted_by(|x, y| x.start.line.cmp(&y.start.line))
|
||||
// .collect();
|
||||
// doc.folding_ranges.update(|symbol| {
|
||||
// symbol.0 = folding;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// self.common
|
||||
// .proxy
|
||||
// .get_lsp_folding_range(path, move |result| {
|
||||
// send(result);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/// Get the current completion lens text
|
||||
pub fn completion_lens(&self) -> Option<String> {
|
||||
self.completion_lens.get_untracked()
|
||||
@@ -923,16 +1137,16 @@ impl Doc {
|
||||
col: usize,
|
||||
) {
|
||||
// TODO: more granular invalidation
|
||||
self.clear_text_cache();
|
||||
self.completion_lens.set(Some(completion_lens));
|
||||
self.completion_pos.set((line, col));
|
||||
self.clear_text_cache();
|
||||
}
|
||||
|
||||
pub fn clear_completion_lens(&self) {
|
||||
// TODO: more granular invalidation
|
||||
if self.completion_lens.get_untracked().is_some() {
|
||||
self.clear_text_cache();
|
||||
self.completion_lens.set(None);
|
||||
self.clear_text_cache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,7 +1193,7 @@ impl Doc {
|
||||
// In normal typing, if we didn't do this, then the text would jitter forward and then
|
||||
// backwards as the completion lens is updated.
|
||||
// TODO: this could also handle simple deletion, but we don't currently keep track of
|
||||
// the past copmletion lens string content in the field.
|
||||
// the past completion lens string content in the field.
|
||||
if delta.as_simple_insert().is_some() {
|
||||
let (iv, new_len) = delta.summary();
|
||||
if iv.start() == iv.end()
|
||||
@@ -1051,7 +1265,7 @@ impl Doc {
|
||||
.set(FindProgress::InProgress(Selection::new()));
|
||||
|
||||
let find_result = self.find_result.clone();
|
||||
let send = create_ext_action(self.scope, move |occurrences| {
|
||||
let send = create_ext_action(self.scope, move |occurrences: Selection| {
|
||||
find_result.occurrences.set(occurrences);
|
||||
find_result.progress.set(FindProgress::Ready);
|
||||
});
|
||||
@@ -1088,11 +1302,7 @@ impl Doc {
|
||||
.iter()
|
||||
.filter_map(|offset| {
|
||||
let l = buffer.line_of_offset(*offset);
|
||||
if l <= line {
|
||||
Some(l)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if l <= line { Some(l) } else { None }
|
||||
})
|
||||
.dedup()
|
||||
.sorted()
|
||||
@@ -1121,13 +1331,13 @@ impl Doc {
|
||||
content, ..
|
||||
}) = result
|
||||
{
|
||||
let hisotry = DocumentHistory::new(
|
||||
let history = DocumentHistory::new(
|
||||
path.clone(),
|
||||
"head".to_string(),
|
||||
&content,
|
||||
);
|
||||
histories.update(|histories| {
|
||||
histories.insert("head".to_string(), hisotry);
|
||||
histories.insert("head".to_string(), history);
|
||||
});
|
||||
|
||||
doc.trigger_head_change();
|
||||
@@ -1219,9 +1429,9 @@ impl Doc {
|
||||
) {
|
||||
// TODO: more granular invalidation
|
||||
batch(|| {
|
||||
self.clear_text_cache();
|
||||
self.inline_completion.set(Some(inline_completion));
|
||||
self.inline_completion_pos.set((line, col));
|
||||
self.clear_text_cache();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1281,9 +1491,11 @@ impl Doc {
|
||||
/// Uses a language aware algorithm if syntax support is available for the current language,
|
||||
/// else falls back to a language unaware algorithm.
|
||||
pub fn find_enclosing_brackets(&self, offset: usize) -> Option<(usize, usize)> {
|
||||
let rev = self.rev();
|
||||
self.syntax
|
||||
.with_untracked(|syntax| {
|
||||
(!syntax.text.is_empty()).then(|| syntax.find_enclosing_pair(offset))
|
||||
(!syntax.text.is_empty() && syntax.rev == rev)
|
||||
.then(|| syntax.find_enclosing_pair(offset))
|
||||
})
|
||||
// If syntax.text is empty, either the buffer is empty or we don't have syntax support
|
||||
// for the current language.
|
||||
@@ -1405,6 +1617,7 @@ impl Document for Doc {
|
||||
self.apply_deltas(&[delta]);
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentPhantom for Doc {
|
||||
fn phantom_text(
|
||||
&self,
|
||||
@@ -1433,9 +1646,46 @@ impl DocumentPhantom for Doc {
|
||||
interval.start >= start_offset && interval.start < end_offset
|
||||
})
|
||||
.map(|(interval, inlay_hint)| {
|
||||
let (_, col) = self
|
||||
.buffer
|
||||
.with_untracked(|b| b.offset_to_line_col(interval.start));
|
||||
let (col, affinity) = self.buffer.with_untracked(|b| {
|
||||
let mut cursor =
|
||||
lapce_xi_rope::Cursor::new(b.text(), interval.start);
|
||||
|
||||
let next_char = cursor.peek_next_codepoint();
|
||||
let prev_char = cursor.prev_codepoint();
|
||||
|
||||
let mut affinity = None;
|
||||
if let Some(prev_char) = prev_char {
|
||||
let c = get_char_property(prev_char);
|
||||
if c == CharClassification::Other {
|
||||
affinity = Some(CursorAffinity::Backward)
|
||||
} else if matches!(
|
||||
c,
|
||||
CharClassification::Lf
|
||||
| CharClassification::Cr
|
||||
| CharClassification::Space
|
||||
) {
|
||||
affinity = Some(CursorAffinity::Forward)
|
||||
}
|
||||
};
|
||||
if affinity.is_none() {
|
||||
if let Some(next_char) = next_char {
|
||||
let c = get_char_property(next_char);
|
||||
if c == CharClassification::Other {
|
||||
affinity = Some(CursorAffinity::Forward)
|
||||
} else if matches!(
|
||||
c,
|
||||
CharClassification::Lf
|
||||
| CharClassification::Cr
|
||||
| CharClassification::Space
|
||||
) {
|
||||
affinity = Some(CursorAffinity::Backward)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (_, col) = b.offset_to_line_col(interval.start);
|
||||
(col, affinity)
|
||||
});
|
||||
let text = match &inlay_hint.label {
|
||||
InlayHintLabel::String(label) => label.to_string(),
|
||||
InlayHintLabel::LabelParts(parts) => {
|
||||
@@ -1446,6 +1696,7 @@ impl DocumentPhantom for Doc {
|
||||
kind: PhantomTextKind::InlayHint,
|
||||
col,
|
||||
text,
|
||||
affinity,
|
||||
fg: Some(config.color(LapceColor::INLAY_HINT_FOREGROUND)),
|
||||
// font_family: Some(config.editor.inlay_hint_font_family()),
|
||||
font_size: Some(config.editor.inlay_hint_font_size()),
|
||||
@@ -1498,18 +1749,22 @@ impl DocumentPhantom for Doc {
|
||||
config.color(theme_prop)
|
||||
};
|
||||
|
||||
let text = if config.editor.error_lens_multiline
|
||||
{
|
||||
format!(" {}", diag.message)
|
||||
} else {
|
||||
format!(
|
||||
" {}",
|
||||
diag.message.lines().join(" ")
|
||||
)
|
||||
};
|
||||
let text =
|
||||
if config.editor.only_render_error_styling {
|
||||
"".to_string()
|
||||
} else if config.editor.error_lens_multiline
|
||||
{
|
||||
format!(" {}", diag.message)
|
||||
} else {
|
||||
format!(
|
||||
" {}",
|
||||
diag.message.lines().join(" ")
|
||||
)
|
||||
};
|
||||
Some(PhantomText {
|
||||
kind: PhantomTextKind::Diagnostic,
|
||||
col: end_offset - start_offset,
|
||||
affinity: Some(CursorAffinity::Backward),
|
||||
text,
|
||||
fg: Some(fg),
|
||||
font_size: Some(
|
||||
@@ -1543,6 +1798,7 @@ impl DocumentPhantom for Doc {
|
||||
text: completion.clone(),
|
||||
fg: Some(config.color(LapceColor::COMPLETION_LENS_FOREGROUND)),
|
||||
font_size: Some(config.editor.completion_lens_font_size()),
|
||||
affinity: Some(CursorAffinity::Backward),
|
||||
// font_family: Some(config.editor.completion_lens_font_family()),
|
||||
bg: None,
|
||||
under_line: None,
|
||||
@@ -1567,6 +1823,7 @@ impl DocumentPhantom for Doc {
|
||||
kind: PhantomTextKind::Completion,
|
||||
col: inline_completion_col,
|
||||
text: completion.clone(),
|
||||
affinity: Some(CursorAffinity::Backward),
|
||||
fg: Some(config.color(LapceColor::COMPLETION_LENS_FOREGROUND)),
|
||||
font_size: Some(config.editor.completion_lens_font_size()),
|
||||
// font_family: Some(config.editor.completion_lens_font_family()),
|
||||
@@ -1663,7 +1920,8 @@ impl DocStyling {
|
||||
if let Some(fg_color) = config.style_color(fg_color) {
|
||||
let start = phantom_text.col_at(bracket_style.start);
|
||||
let end = phantom_text.col_at(bracket_style.end);
|
||||
attrs_list.add_span(start..end, attrs.color(fg_color));
|
||||
attrs_list
|
||||
.add_span(start..end, attrs.clone().color(fg_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1689,23 +1947,23 @@ impl Styling for DocStyling {
|
||||
&self,
|
||||
_: EditorId,
|
||||
_line: usize,
|
||||
) -> std::borrow::Cow<[floem::cosmic_text::FamilyOwned]> {
|
||||
) -> std::borrow::Cow<'_, [floem::text::FamilyOwned]> {
|
||||
// TODO: cache this
|
||||
Cow::Owned(self.config.with_untracked(|config| {
|
||||
FamilyOwned::parse_list(&config.editor.font_family).collect()
|
||||
}))
|
||||
}
|
||||
|
||||
fn weight(&self, _: EditorId, _line: usize) -> floem::cosmic_text::Weight {
|
||||
floem::cosmic_text::Weight::NORMAL
|
||||
fn weight(&self, _: EditorId, _line: usize) -> floem::text::Weight {
|
||||
floem::text::Weight::NORMAL
|
||||
}
|
||||
|
||||
fn italic_style(&self, _: EditorId, _line: usize) -> floem::cosmic_text::Style {
|
||||
floem::cosmic_text::Style::Normal
|
||||
fn italic_style(&self, _: EditorId, _line: usize) -> floem::text::Style {
|
||||
floem::text::Style::Normal
|
||||
}
|
||||
|
||||
fn stretch(&self, _: EditorId, _line: usize) -> floem::cosmic_text::Stretch {
|
||||
floem::cosmic_text::Stretch::Normal
|
||||
fn stretch(&self, _: EditorId, _line: usize) -> floem::text::Stretch {
|
||||
floem::text::Stretch::Normal
|
||||
}
|
||||
|
||||
fn indent_line(&self, _: EditorId, line: usize, line_content: &str) -> usize {
|
||||
@@ -1749,7 +2007,7 @@ impl Styling for DocStyling {
|
||||
if let Some(fg_color) = config.style_color(fg_color) {
|
||||
let start = phantom_text.col_at(line_style.start);
|
||||
let end = phantom_text.col_at(line_style.end);
|
||||
attrs_list.add_span(start..end, default.color(fg_color));
|
||||
attrs_list.add_span(start..end, default.clone().color(fg_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1804,11 +2062,7 @@ impl Styling for DocStyling {
|
||||
&& end >= start_offset
|
||||
&& diag.severity < Some(DiagnosticSeverity::HINT)
|
||||
{
|
||||
let start = if start > start_offset {
|
||||
start - start_offset
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let start = start.saturating_sub(start_offset);
|
||||
let end = end - start_offset;
|
||||
let start = phantom_text.col_after(start, true);
|
||||
let end = phantom_text.col_after(end, false);
|
||||
@@ -1910,10 +2164,12 @@ fn syntax_prev_unmatched(
|
||||
|
||||
fn should_blink(
|
||||
focus: RwSignal<Focus>,
|
||||
keyboard_focus: RwSignal<Option<floem::id::Id>>,
|
||||
keyboard_focus: RwSignal<Option<ViewId>>,
|
||||
) -> impl Fn() -> bool {
|
||||
move || {
|
||||
let focus = focus.get_untracked();
|
||||
let Some(focus) = focus.try_get_untracked() else {
|
||||
return false;
|
||||
};
|
||||
if matches!(
|
||||
focus,
|
||||
Focus::Workbench
|
||||
@@ -1967,9 +2223,8 @@ fn extra_styles_for_range(
|
||||
return None;
|
||||
}
|
||||
|
||||
let y = (run.line_height - run.glyph_ascent - run.glyph_descent) as f64
|
||||
/ 2.0;
|
||||
let height = (run.glyph_ascent + run.glyph_descent) as f64;
|
||||
let height = (run.max_ascent + run.max_descent) as f64;
|
||||
let y = run.line_y as f64 - run.max_ascent as f64;
|
||||
|
||||
Some(LineExtraStyle {
|
||||
x,
|
||||
|
||||
+876
-119
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,24 @@
|
||||
use std::{rc::Rc, sync::atomic};
|
||||
|
||||
use floem::{
|
||||
event::EventListener,
|
||||
View,
|
||||
event::{Event, EventListener},
|
||||
ext_event::create_ext_action,
|
||||
reactive::{RwSignal, Scope},
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
style::CursorStyle,
|
||||
view::View,
|
||||
views::{
|
||||
clip, dyn_stack, editor::id::EditorId, empty, label, stack, svg, Decorators,
|
||||
Decorators, clip, dyn_stack, editor::id::EditorId, empty, label, stack, svg,
|
||||
},
|
||||
};
|
||||
use lapce_core::buffer::{
|
||||
diff::{expand_diff_lines, rope_diff, DiffExpand, DiffLines},
|
||||
diff::{DiffExpand, DiffLines, expand_diff_lines, rope_diff},
|
||||
rope_text::RopeText,
|
||||
};
|
||||
use lapce_rpc::{buffer::BufferId, proxy::ProxyResponse};
|
||||
use lapce_xi_rope::Rope;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{EditorData, EditorViewKind};
|
||||
use crate::{
|
||||
config::{color::LapceColor, icon::LapceIcons},
|
||||
doc::{Doc, DocContent},
|
||||
@@ -27,8 +28,6 @@ use crate::{
|
||||
window_tab::CommonData,
|
||||
};
|
||||
|
||||
use super::{EditorData, EditorViewKind};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DiffInfo {
|
||||
pub is_right: bool,
|
||||
@@ -56,7 +55,7 @@ impl DiffEditorInfo {
|
||||
let common = data.common.clone();
|
||||
move |content: &DocContent| match content {
|
||||
DocContent::File { path, .. } => {
|
||||
let (doc, _) = data.get_doc(path.clone());
|
||||
let (doc, _) = data.get_doc(path.clone(), None);
|
||||
doc
|
||||
}
|
||||
DocContent::Local => {
|
||||
@@ -306,87 +305,58 @@ impl DiffEditorData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct DiffShowMoreSection {
|
||||
left_actual_line: usize,
|
||||
right_actual_line: usize,
|
||||
visual_line: usize,
|
||||
skip_start: usize,
|
||||
lines: usize,
|
||||
}
|
||||
|
||||
pub fn diff_show_more_section_view(
|
||||
left_editor: &EditorData,
|
||||
right_editor: &EditorData,
|
||||
) -> impl View {
|
||||
) -> impl View + use<> {
|
||||
let left_editor_view = left_editor.kind;
|
||||
let right_editor_view = right_editor.kind;
|
||||
let right_screen_lines = right_editor.screen_lines();
|
||||
let right_scroll_delta = right_editor.editor.scroll_delta;
|
||||
let viewport = right_editor.viewport();
|
||||
let config = right_editor.common.config;
|
||||
|
||||
let each_fn = move || {
|
||||
let editor_view = right_editor_view.get();
|
||||
|
||||
if let EditorViewKind::Diff(diff_info) = editor_view {
|
||||
let viewport = viewport.get();
|
||||
let config = config.get_untracked();
|
||||
let line_height = config.editor.line_height() as f64;
|
||||
diff_info
|
||||
.changes
|
||||
.iter()
|
||||
.filter_map(|change| {
|
||||
let DiffLines::Both(info) = change else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let min_line = (viewport.y0 / line_height).floor() as usize;
|
||||
let max_line = (viewport.y1 / line_height).ceil() as usize;
|
||||
let skip = info.skip.as_ref()?;
|
||||
|
||||
let mut visual_line = 0;
|
||||
let mut last_change: Option<&DiffLines> = None;
|
||||
let mut changes = diff_info.changes.iter().peekable();
|
||||
let mut sections = Vec::new();
|
||||
while let Some(change) = changes.next() {
|
||||
match change {
|
||||
DiffLines::Left(range) => {
|
||||
if let Some(DiffLines::Right(_)) = changes.peek() {
|
||||
} else {
|
||||
let len = range.len();
|
||||
visual_line += len;
|
||||
}
|
||||
}
|
||||
DiffLines::Right(range) => {
|
||||
let len = range.len();
|
||||
visual_line += len;
|
||||
|
||||
if let Some(DiffLines::Left(r)) = last_change {
|
||||
let len = r.len() - r.len().min(range.len());
|
||||
if len > 0 {
|
||||
visual_line += len;
|
||||
}
|
||||
};
|
||||
}
|
||||
DiffLines::Both(info) => {
|
||||
if let Some(skip) = info.skip.as_ref() {
|
||||
visual_line += skip.start;
|
||||
if visual_line + 1 >= min_line {
|
||||
sections.push(DiffShowMoreSection {
|
||||
left_actual_line: info.left.start,
|
||||
right_actual_line: info.right.start,
|
||||
visual_line,
|
||||
lines: skip.len(),
|
||||
});
|
||||
}
|
||||
visual_line += 1;
|
||||
visual_line += info.right.len() - skip.end;
|
||||
} else {
|
||||
visual_line += info.right.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
if visual_line > max_line {
|
||||
break;
|
||||
}
|
||||
last_change = Some(change);
|
||||
}
|
||||
sections
|
||||
Some(DiffShowMoreSection {
|
||||
left_actual_line: info.left.start,
|
||||
right_actual_line: info.right.start,
|
||||
skip_start: skip.start,
|
||||
lines: skip.len(),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let key_fn =
|
||||
move |section: &DiffShowMoreSection| (section.visual_line, section.lines);
|
||||
let key_fn = move |section: &DiffShowMoreSection| {
|
||||
(
|
||||
section.right_actual_line + section.skip_start,
|
||||
section.lines,
|
||||
)
|
||||
};
|
||||
|
||||
let view_fn = move |section: DiffShowMoreSection| {
|
||||
stack((
|
||||
@@ -518,17 +488,46 @@ pub fn diff_show_more_section_view(
|
||||
.hover(|s| s.cursor(CursorStyle::Pointer))
|
||||
}),
|
||||
))
|
||||
.on_event_cont(EventListener::PointerWheel, move |event| {
|
||||
if let Event::PointerWheel(event) = event {
|
||||
right_scroll_delta.set(event.delta);
|
||||
}
|
||||
})
|
||||
.style(move |s| {
|
||||
let right_screen_lines = right_screen_lines.get();
|
||||
|
||||
let mut right_line = section.right_actual_line + section.skip_start;
|
||||
let is_before_skip = if right_line > 0 {
|
||||
right_line -= 1;
|
||||
true
|
||||
} else {
|
||||
right_line += section.lines;
|
||||
false
|
||||
};
|
||||
|
||||
let Some(line_info) = right_screen_lines.info_for_line(right_line)
|
||||
else {
|
||||
return s.hide();
|
||||
};
|
||||
|
||||
let config = config.get();
|
||||
let line_height = config.editor.line_height();
|
||||
|
||||
let mut y = line_info.y - viewport.get().y0;
|
||||
|
||||
if is_before_skip {
|
||||
y += line_height as f64
|
||||
} else {
|
||||
y -= line_height as f64
|
||||
}
|
||||
|
||||
s.absolute()
|
||||
.width_pct(100.0)
|
||||
.height(config.editor.line_height() as f32)
|
||||
.height(line_height as f32)
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.margin_top(
|
||||
(section.visual_line * config.editor.line_height()) as f32
|
||||
- viewport.get().y0 as f32,
|
||||
)
|
||||
.margin_top(y)
|
||||
.pointer_events_auto()
|
||||
.hover(|s| s.cursor(CursorStyle::Default))
|
||||
})
|
||||
};
|
||||
@@ -543,5 +542,11 @@ pub fn diff_show_more_section_view(
|
||||
)
|
||||
.style(|s| s.size_pct(100.0, 100.0)),
|
||||
))
|
||||
.style(|s| s.absolute().flex_col().size_pct(100.0, 100.0))
|
||||
.style(|s| {
|
||||
s.absolute()
|
||||
.flex_col()
|
||||
.size_pct(100.0, 100.0)
|
||||
.pointer_events_none()
|
||||
})
|
||||
.debug_name("Diff Show More Section")
|
||||
}
|
||||
|
||||
+235
-44
@@ -1,32 +1,35 @@
|
||||
use floem::{
|
||||
Renderer, View, ViewId,
|
||||
context::PaintCx,
|
||||
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout},
|
||||
id::Id,
|
||||
peniko::kurbo::{Point, Rect, Size},
|
||||
view::{AnyWidget, View, ViewData, Widget},
|
||||
Renderer,
|
||||
reactive::{Memo, SignalGet, SignalWith},
|
||||
text::{Attrs, AttrsList, FamilyOwned, TextLayout},
|
||||
};
|
||||
use im::HashMap;
|
||||
use lapce_core::{buffer::rope_text::RopeText, mode::Mode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::{color::LapceColor, LapceConfig};
|
||||
|
||||
use super::{view::changes_colors_screen, EditorData};
|
||||
use super::{EditorData, view::changes_colors_screen};
|
||||
use crate::config::{LapceConfig, color::LapceColor};
|
||||
|
||||
pub struct EditorGutterView {
|
||||
id: Id,
|
||||
data: ViewData,
|
||||
id: ViewId,
|
||||
editor: EditorData,
|
||||
width: f64,
|
||||
gutter_padding_right: Memo<f32>,
|
||||
}
|
||||
|
||||
pub fn editor_gutter_view(editor: EditorData) -> EditorGutterView {
|
||||
let id = Id::next();
|
||||
pub fn editor_gutter_view(
|
||||
editor: EditorData,
|
||||
gutter_padding_right: Memo<f32>,
|
||||
) -> EditorGutterView {
|
||||
let id = ViewId::new();
|
||||
|
||||
EditorGutterView {
|
||||
id,
|
||||
data: ViewData::new(id),
|
||||
editor,
|
||||
width: 0.0,
|
||||
gutter_padding_right,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +48,7 @@ impl EditorGutterView {
|
||||
|
||||
let changes = e_data.doc().head_changes().get_untracked();
|
||||
let line_height = config.editor.line_height() as f64;
|
||||
let gutter_padding_right = self.gutter_padding_right.get_untracked() as f64;
|
||||
|
||||
let changes = changes_colors_screen(config, &e_data.editor, changes);
|
||||
for (y, height, removed, color) in changes {
|
||||
@@ -58,9 +62,10 @@ impl EditorGutterView {
|
||||
y -= 5.0;
|
||||
}
|
||||
cx.fill(
|
||||
&Size::new(3.0, height)
|
||||
.to_rect()
|
||||
.with_origin(Point::new(self.width + 7.0, y)),
|
||||
&Size::new(3.0, height).to_rect().with_origin(Point::new(
|
||||
self.width + 5.0 - gutter_padding_right,
|
||||
y,
|
||||
)),
|
||||
color,
|
||||
0.0,
|
||||
)
|
||||
@@ -80,8 +85,7 @@ impl EditorGutterView {
|
||||
if !config.editor.sticky_header {
|
||||
return;
|
||||
}
|
||||
let sticky_header_height = self.editor.sticky_header_height;
|
||||
let sticky_header_height = sticky_header_height.get_untracked();
|
||||
let sticky_header_height = self.editor.sticky_header_height.get_untracked();
|
||||
if sticky_header_height == 0.0 {
|
||||
return;
|
||||
}
|
||||
@@ -105,32 +109,15 @@ impl EditorGutterView {
|
||||
}
|
||||
|
||||
impl View for EditorGutterView {
|
||||
fn view_data(&self) -> &ViewData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn view_data_mut(&mut self) -> &mut ViewData {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
fn build(self) -> AnyWidget {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
impl Widget for EditorGutterView {
|
||||
fn view_data(&self) -> &ViewData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn view_data_mut(&mut self) -> &mut ViewData {
|
||||
&mut self.data
|
||||
fn id(&self) -> ViewId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn compute_layout(
|
||||
&mut self,
|
||||
cx: &mut floem::context::ComputeLayoutCx,
|
||||
_cx: &mut floem::context::ComputeLayoutCx,
|
||||
) -> Option<floem::peniko::kurbo::Rect> {
|
||||
if let Some(width) = cx.get_layout(self.id).map(|l| l.size.width as f64) {
|
||||
if let Some(width) = self.id.get_layout().map(|l| l.size.width as f64) {
|
||||
self.width = width;
|
||||
}
|
||||
None
|
||||
@@ -160,9 +147,12 @@ impl Widget for EditorGutterView {
|
||||
.family(&family)
|
||||
.color(config.color(LapceColor::EDITOR_DIM))
|
||||
.font_size(config.editor.font_size() as f32);
|
||||
let attrs_list = AttrsList::new(attrs);
|
||||
let current_line_attrs_list =
|
||||
AttrsList::new(attrs.color(config.color(LapceColor::EDITOR_FOREGROUND)));
|
||||
let attrs_list = AttrsList::new(attrs.clone());
|
||||
let current_line_attrs_list = AttrsList::new(
|
||||
attrs
|
||||
.clone()
|
||||
.color(config.color(LapceColor::EDITOR_FOREGROUND)),
|
||||
);
|
||||
let show_relative = config.core.modal
|
||||
&& config.editor.modal_mode_relative_line_numbers
|
||||
&& mode != Mode::Insert
|
||||
@@ -188,9 +178,13 @@ impl Widget for EditorGutterView {
|
||||
|
||||
let mut text_layout = TextLayout::new();
|
||||
if line == current_line {
|
||||
text_layout.set_text(&text, current_line_attrs_list.clone());
|
||||
text_layout.set_text(
|
||||
&text,
|
||||
current_line_attrs_list.clone(),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
text_layout.set_text(&text, attrs_list.clone());
|
||||
text_layout.set_text(&text, attrs_list.clone(), None);
|
||||
}
|
||||
let size = text_layout.size();
|
||||
let height = size.height;
|
||||
@@ -198,7 +192,10 @@ impl Widget for EditorGutterView {
|
||||
cx.draw_text(
|
||||
&text_layout,
|
||||
Point::new(
|
||||
(self.width - (size.width)).max(0.0),
|
||||
(self.width
|
||||
- size.width
|
||||
- self.gutter_padding_right.get_untracked() as f64)
|
||||
.max(0.0),
|
||||
y + (line_height - height) / 2.0 - viewport.y0,
|
||||
),
|
||||
);
|
||||
@@ -208,4 +205,198 @@ impl Widget for EditorGutterView {
|
||||
self.paint_head_changes(cx, &self.editor, viewport, kind_is_normal, &config);
|
||||
self.paint_sticky_headers(cx, kind_is_normal, &config);
|
||||
}
|
||||
|
||||
fn debug_name(&self) -> std::borrow::Cow<'static, str> {
|
||||
"Editor Gutter".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FoldingRanges(pub Vec<FoldingRange>);
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FoldedRanges(pub Vec<FoldedRange>);
|
||||
|
||||
impl FoldingRanges {
|
||||
pub fn get_folded_range(&self) -> FoldedRanges {
|
||||
let mut range = Vec::new();
|
||||
let mut limit_line = 0;
|
||||
for item in &self.0 {
|
||||
if item.start.line < limit_line && limit_line > 0 {
|
||||
continue;
|
||||
}
|
||||
if item.status.is_folded() {
|
||||
range.push(crate::editor::gutter::FoldedRange {
|
||||
start: item.start,
|
||||
end: item.end,
|
||||
});
|
||||
limit_line = item.end.line;
|
||||
}
|
||||
}
|
||||
|
||||
FoldedRanges(range)
|
||||
}
|
||||
pub fn to_display_items(&self) -> Vec<FoldingDisplayItem> {
|
||||
let mut folded = HashMap::new();
|
||||
let mut unfold_start: HashMap<u32, FoldingDisplayItem> = HashMap::new();
|
||||
let mut unfold_end = HashMap::new();
|
||||
let mut limit_line = 0;
|
||||
for item in &self.0 {
|
||||
if item.start.line < limit_line && limit_line > 0 {
|
||||
continue;
|
||||
}
|
||||
match item.status {
|
||||
FoldingRangeStatus::Fold => {
|
||||
folded.insert(
|
||||
item.start.line,
|
||||
FoldingDisplayItem::Folded(item.start),
|
||||
);
|
||||
limit_line = item.end.line;
|
||||
}
|
||||
FoldingRangeStatus::Unfold => {
|
||||
unfold_start.insert(
|
||||
item.start.line,
|
||||
FoldingDisplayItem::UnfoldStart(item.start),
|
||||
);
|
||||
unfold_end.insert(
|
||||
item.end.line,
|
||||
FoldingDisplayItem::UnfoldEnd(item.end),
|
||||
);
|
||||
limit_line = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
for (key, val) in unfold_end {
|
||||
unfold_start.insert(key, val);
|
||||
}
|
||||
for (key, val) in folded {
|
||||
unfold_start.insert(key, val);
|
||||
}
|
||||
|
||||
unfold_start.into_iter().map(|x| x.1).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FoldedRanges {
|
||||
pub fn contain_line(&self, start_index: usize, line: u32) -> (bool, usize) {
|
||||
if start_index >= self.0.len() {
|
||||
return (false, start_index);
|
||||
}
|
||||
let mut last_index = start_index;
|
||||
for range in self.0[start_index..].iter() {
|
||||
if range.start.line >= line {
|
||||
return (false, last_index);
|
||||
} else if range.start.line < line && range.end.line > line {
|
||||
return (true, last_index);
|
||||
} else if range.end.line < line {
|
||||
last_index += 1;
|
||||
}
|
||||
}
|
||||
(false, last_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FoldedRange {
|
||||
pub start: FoldingPosition,
|
||||
pub end: FoldingPosition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FoldingRange {
|
||||
pub start: FoldingPosition,
|
||||
pub end: FoldingPosition,
|
||||
pub status: FoldingRangeStatus,
|
||||
pub collapsed_text: Option<String>,
|
||||
}
|
||||
|
||||
impl FoldingRange {
|
||||
pub fn from_lsp(value: lsp_types::FoldingRange) -> Self {
|
||||
let lsp_types::FoldingRange {
|
||||
start_line,
|
||||
start_character,
|
||||
end_line,
|
||||
end_character,
|
||||
collapsed_text,
|
||||
..
|
||||
} = value;
|
||||
let status = FoldingRangeStatus::Unfold;
|
||||
Self {
|
||||
start: FoldingPosition {
|
||||
line: start_line,
|
||||
character: start_character,
|
||||
// kind: kind.clone().map(|x| FoldingRangeKind::from(x)),
|
||||
},
|
||||
end: FoldingPosition {
|
||||
line: end_line,
|
||||
character: end_character,
|
||||
// kind: kind.map(|x| FoldingRangeKind::from(x)),
|
||||
},
|
||||
status,
|
||||
collapsed_text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
|
||||
pub struct FoldingPosition {
|
||||
pub line: u32,
|
||||
pub character: Option<u32>,
|
||||
// pub kind: Option<FoldingRangeKind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||
pub enum FoldingRangeStatus {
|
||||
Fold,
|
||||
#[default]
|
||||
Unfold,
|
||||
}
|
||||
|
||||
impl FoldingRangeStatus {
|
||||
pub fn click(&mut self) {
|
||||
// match self {
|
||||
// FoldingRangeStatus::Fold => {
|
||||
// *self = FoldingRangeStatus::Unfold;
|
||||
// }
|
||||
// FoldingRangeStatus::Unfold => {
|
||||
// *self = FoldingRangeStatus::Fold;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
pub fn is_folded(&self) -> bool {
|
||||
*self == Self::Fold
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum FoldingDisplayItem {
|
||||
UnfoldStart(FoldingPosition),
|
||||
Folded(FoldingPosition),
|
||||
UnfoldEnd(FoldingPosition),
|
||||
}
|
||||
|
||||
impl FoldingDisplayItem {
|
||||
pub fn position(&self) -> FoldingPosition {
|
||||
match self {
|
||||
FoldingDisplayItem::UnfoldStart(x) => *x,
|
||||
FoldingDisplayItem::Folded(x) => *x,
|
||||
FoldingDisplayItem::UnfoldEnd(x) => *x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize, Clone, Hash, Copy)]
|
||||
pub enum FoldingRangeKind {
|
||||
Comment,
|
||||
Imports,
|
||||
Region,
|
||||
}
|
||||
|
||||
impl From<lsp_types::FoldingRangeKind> for FoldingRangeKind {
|
||||
fn from(value: lsp_types::FoldingRangeKind) -> Self {
|
||||
match value {
|
||||
lsp_types::FoldingRangeKind::Comment => FoldingRangeKind::Comment,
|
||||
lsp_types::FoldingRangeKind::Imports => FoldingRangeKind::Imports,
|
||||
lsp_types::FoldingRangeKind::Region => FoldingRangeKind::Region,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+837
-507
File diff suppressed because it is too large
Load Diff
+30
-19
@@ -6,22 +6,25 @@ use std::{
|
||||
|
||||
use floem::{
|
||||
peniko::{
|
||||
kurbo::{Point, Rect},
|
||||
Color,
|
||||
kurbo::{Point, Rect},
|
||||
},
|
||||
reactive::{
|
||||
Memo, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith,
|
||||
create_memo, create_rw_signal,
|
||||
},
|
||||
reactive::{create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, Scope},
|
||||
views::editor::id::EditorId,
|
||||
};
|
||||
use lapce_rpc::plugin::VoltID;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor, icon::LapceIcons},
|
||||
doc::{Doc, DocContent},
|
||||
editor::{
|
||||
EditorData, EditorInfo,
|
||||
diff::{DiffEditorData, DiffEditorInfo},
|
||||
location::EditorLocation,
|
||||
EditorData, EditorInfo,
|
||||
},
|
||||
id::{
|
||||
DiffEditorId, EditorTabId, KeymapId, SettingsId, SplitId,
|
||||
@@ -144,7 +147,8 @@ pub enum EditorTabChild {
|
||||
pub struct EditorTabChildViewInfo {
|
||||
pub icon: String,
|
||||
pub color: Option<Color>,
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub path: Option<PathBuf>,
|
||||
pub confirmed: Option<RwSignal<bool>>,
|
||||
pub is_pristine: bool,
|
||||
}
|
||||
@@ -225,9 +229,9 @@ impl EditorTabChild {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (icon, color, path, confirmed, is_pristine) = match path {
|
||||
Some((path, confirmed, is_pritine)) => {
|
||||
let (svg, color) = config.file_svg(&path);
|
||||
let (icon, color, name, confirmed, is_pristine) = match path {
|
||||
Some((ref path, confirmed, is_pritine)) => {
|
||||
let (svg, color) = config.file_svg(path);
|
||||
(
|
||||
svg,
|
||||
color,
|
||||
@@ -250,7 +254,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon,
|
||||
color,
|
||||
path,
|
||||
name,
|
||||
path: path.map(|opt| opt.0),
|
||||
confirmed: Some(confirmed),
|
||||
is_pristine,
|
||||
}
|
||||
@@ -301,8 +306,10 @@ impl EditorTabChild {
|
||||
is_pristine,
|
||||
)
|
||||
}
|
||||
[Some((left_path, left_is_pristine)), Some((right_path, right_is_pristine))] =>
|
||||
{
|
||||
[
|
||||
Some((left_path, left_is_pristine)),
|
||||
Some((right_path, right_is_pristine)),
|
||||
] => {
|
||||
let (svg, color) =
|
||||
config.files_svg(&[&left_path, &right_path]);
|
||||
let [left_file_name, right_file_name] =
|
||||
@@ -328,7 +335,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon,
|
||||
color,
|
||||
path,
|
||||
name: path,
|
||||
path: None,
|
||||
confirmed,
|
||||
is_pristine,
|
||||
}
|
||||
@@ -338,7 +346,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon: config.ui_svg(LapceIcons::SETTINGS),
|
||||
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
|
||||
path: "Settings".to_string(),
|
||||
name: "Settings".to_string(),
|
||||
path: None,
|
||||
confirmed: None,
|
||||
is_pristine: true,
|
||||
}
|
||||
@@ -348,7 +357,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon: config.ui_svg(LapceIcons::SYMBOL_COLOR),
|
||||
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
|
||||
path: "Theme Colors".to_string(),
|
||||
name: "Theme Colors".to_string(),
|
||||
path: None,
|
||||
confirmed: None,
|
||||
is_pristine: true,
|
||||
}
|
||||
@@ -358,7 +368,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon: config.ui_svg(LapceIcons::KEYBOARD),
|
||||
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
|
||||
path: "Keyboard Shortcuts".to_string(),
|
||||
name: "Keyboard Shortcuts".to_string(),
|
||||
path: None,
|
||||
confirmed: None,
|
||||
is_pristine: true,
|
||||
}
|
||||
@@ -381,7 +392,8 @@ impl EditorTabChild {
|
||||
EditorTabChildViewInfo {
|
||||
icon: config.ui_svg(LapceIcons::EXTENSIONS),
|
||||
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
|
||||
path: display_name,
|
||||
name: display_name,
|
||||
path: None,
|
||||
confirmed: None,
|
||||
is_pristine: true,
|
||||
}
|
||||
@@ -458,7 +470,7 @@ impl EditorTabData {
|
||||
}
|
||||
|
||||
pub fn tab_info(&self, data: &WindowTabData) -> EditorTabInfo {
|
||||
let info = EditorTabInfo {
|
||||
EditorTabInfo {
|
||||
active: self.active,
|
||||
is_focus: data.main_split.active_editor_tab.get_untracked()
|
||||
== Some(self.editor_tab_id),
|
||||
@@ -467,7 +479,6 @@ impl EditorTabData {
|
||||
.iter()
|
||||
.map(|(_, _, child)| child.child_info(data))
|
||||
.collect(),
|
||||
};
|
||||
info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,17 @@ use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use floem::{
|
||||
action::show_context_menu,
|
||||
event::EventPropagation,
|
||||
ext_event::create_ext_action,
|
||||
keyboard::Modifiers,
|
||||
menu::{Menu, MenuItem},
|
||||
reactive::{RwSignal, Scope},
|
||||
reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
views::editor::text::SystemClipboard,
|
||||
EventPropagation,
|
||||
};
|
||||
use globset::Glob;
|
||||
use lapce_core::{
|
||||
@@ -22,14 +23,18 @@ use lapce_core::{
|
||||
register::Clipboard,
|
||||
};
|
||||
use lapce_rpc::{
|
||||
file::{Duplicating, FileNodeItem, Naming, NamingState, NewNode, Renaming},
|
||||
file::{
|
||||
Duplicating, FileNodeItem, FileNodeViewKind, Naming, NamingState, NewNode,
|
||||
Renaming,
|
||||
},
|
||||
proxy::ProxyResponse,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command::{CommandExecuted, CommandKind, InternalCommand, LapceCommand},
|
||||
config::LapceConfig,
|
||||
editor::EditorData,
|
||||
keypress::{condition::Condition, KeyPressFocus},
|
||||
keypress::{KeyPressFocus, condition::Condition},
|
||||
main_split::Editors,
|
||||
window_tab::CommonData,
|
||||
};
|
||||
@@ -43,13 +48,15 @@ enum RenamedPath {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileExplorerData {
|
||||
pub root: RwSignal<FileNodeItem>,
|
||||
pub naming: RwSignal<Naming>,
|
||||
pub naming_editor_data: EditorData,
|
||||
pub common: Rc<CommonData>,
|
||||
pub scroll_to_line: RwSignal<Option<f64>>,
|
||||
left_diff_path: RwSignal<Option<PathBuf>>,
|
||||
pub select: RwSignal<Option<FileNodeViewKind>>,
|
||||
}
|
||||
|
||||
impl KeyPressFocus for FileExplorerData {
|
||||
@@ -128,7 +135,9 @@ impl FileExplorerData {
|
||||
naming,
|
||||
naming_editor_data,
|
||||
common,
|
||||
scroll_to_line: cx.create_rw_signal(None),
|
||||
left_diff_path: cx.create_rw_signal(None),
|
||||
select: cx.create_rw_signal(None),
|
||||
};
|
||||
if data.common.workspace.path.is_some() {
|
||||
// only fill in the child files if there is open folder
|
||||
@@ -147,26 +156,22 @@ impl FileExplorerData {
|
||||
/// Toggle whether the directory is expanded or not.
|
||||
/// Does nothing if the path does not exist or is not a directory.
|
||||
pub fn toggle_expand(&self, path: &Path) {
|
||||
let Some(read) = self
|
||||
.root
|
||||
.try_update(|root| {
|
||||
let read = if let Some(node) = root.get_file_node_mut(path) {
|
||||
if !node.is_dir {
|
||||
return None;
|
||||
}
|
||||
node.open = !node.open;
|
||||
Some(node.read)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if Some(true) == read {
|
||||
root.update_node_count_recursive(path);
|
||||
let Some(Some(read)) = self.root.try_update(|root| {
|
||||
let read = if let Some(node) = root.get_file_node_mut(path) {
|
||||
if !node.is_dir {
|
||||
return None;
|
||||
}
|
||||
read
|
||||
})
|
||||
.unwrap()
|
||||
else {
|
||||
node.open = !node.open;
|
||||
Some(node.read)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if Some(true) == read {
|
||||
root.update_node_count_recursive(path);
|
||||
}
|
||||
read
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -387,10 +392,10 @@ impl FileExplorerData {
|
||||
self.naming.set(Naming::None);
|
||||
}
|
||||
|
||||
pub fn click(&self, path: &Path) {
|
||||
pub fn click(&self, path: &Path, config: ReadSignal<Arc<LapceConfig>>) {
|
||||
if self.is_dir(path) {
|
||||
self.toggle_expand(path);
|
||||
} else {
|
||||
} else if !config.get_untracked().core.file_explorer_double_click {
|
||||
self.common
|
||||
.internal_command
|
||||
.send(InternalCommand::OpenFile {
|
||||
@@ -399,9 +404,73 @@ impl FileExplorerData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn double_click(&self, path: &Path) -> EventPropagation {
|
||||
pub fn reveal_in_file_tree(&self, path: PathBuf) {
|
||||
let done = self
|
||||
.root
|
||||
.try_update(|root| {
|
||||
// the directories in which the file are located are all read and opened
|
||||
if root.get_file_node(&path).is_some() {
|
||||
for current_path in path.ancestors() {
|
||||
if let Some(file) = root.get_file_node_mut(current_path) {
|
||||
if file.is_dir {
|
||||
file.open = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
root.update_node_count_recursive(&path);
|
||||
true
|
||||
} else {
|
||||
// read and open the directories in which the file are located
|
||||
let mut read_dir = None;
|
||||
// Whether the file is in the workspace
|
||||
let mut exist = false;
|
||||
for current_path in path.ancestors() {
|
||||
if let Some(file) = root.get_file_node_mut(current_path) {
|
||||
exist = true;
|
||||
if file.is_dir {
|
||||
file.open = true;
|
||||
if !file.read {
|
||||
read_dir = Some(current_path.to_path_buf())
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let (true, Some(dir)) = (exist, read_dir) {
|
||||
let explorer = self.clone();
|
||||
let select_path = path.clone();
|
||||
self.read_dir_cb(&dir, move |_| {
|
||||
explorer.reveal_in_file_tree(select_path);
|
||||
})
|
||||
}
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if done {
|
||||
let (found, line) =
|
||||
self.root.with_untracked(|x| x.find_file_at_line(&path));
|
||||
if found {
|
||||
self.scroll_to_line.set(Some(line));
|
||||
self.select.set(Some(FileNodeViewKind::Path(path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn double_click(
|
||||
&self,
|
||||
path: &Path,
|
||||
config: ReadSignal<Arc<LapceConfig>>,
|
||||
) -> EventPropagation {
|
||||
if self.is_dir(path) {
|
||||
EventPropagation::Continue
|
||||
} else if config.get_untracked().core.file_explorer_double_click {
|
||||
self.common.internal_command.send(
|
||||
InternalCommand::OpenAndConfirmedFile {
|
||||
path: path.to_path_buf(),
|
||||
},
|
||||
);
|
||||
EventPropagation::Stop
|
||||
} else {
|
||||
self.common
|
||||
.internal_command
|
||||
@@ -486,21 +555,23 @@ impl FileExplorerData {
|
||||
// TODO: there are situations where we can open the file explorer to remote files
|
||||
if !common.workspace.kind.is_remote() {
|
||||
let path = path_a.clone();
|
||||
menu = menu.entry(MenuItem::new("Reveal in file explorer").action(
|
||||
move || {
|
||||
let path = path.parent().unwrap_or(&path);
|
||||
if !path.exists() {
|
||||
return;
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let title = "Reveal in system file explorer";
|
||||
#[cfg(target_os = "macos")]
|
||||
let title = "Reveal in Finder";
|
||||
menu = menu.entry(MenuItem::new(title).action(move || {
|
||||
let path = path.parent().unwrap_or(&path);
|
||||
if !path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = open::that(path) {
|
||||
tracing::error!(
|
||||
"Failed to reveal file in system file explorer: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
},
|
||||
));
|
||||
if let Err(err) = open::that(path) {
|
||||
tracing::error!(
|
||||
"Failed to reveal file in system file explorer: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if !is_workspace {
|
||||
|
||||
@@ -17,7 +17,7 @@ impl FileNodeVirtualList {
|
||||
|
||||
impl VirtualVector<FileNodeViewData> for FileNodeVirtualList {
|
||||
fn total_len(&self) -> usize {
|
||||
self.file_node_item.children_open_count
|
||||
self.file_node_item.children_open_count + 1
|
||||
}
|
||||
|
||||
fn slice(
|
||||
@@ -31,7 +31,7 @@ impl VirtualVector<FileNodeViewData> for FileNodeVirtualList {
|
||||
let max = range.end;
|
||||
let mut view_items = Vec::new();
|
||||
|
||||
root.append_children_view_slice(&mut view_items, naming, min, max, 0, 0);
|
||||
root.append_view_slice(&mut view_items, naming, min, max, 0, 1);
|
||||
|
||||
view_items.into_iter()
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use std::{path::Path, rc::Rc, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
cosmic_text::Style as FontStyle,
|
||||
View,
|
||||
event::{Event, EventListener},
|
||||
kurbo::Rect,
|
||||
peniko::Color,
|
||||
reactive::{create_rw_signal, ReadSignal, RwSignal},
|
||||
reactive::{
|
||||
ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, create_rw_signal,
|
||||
},
|
||||
style::{AlignItems, CursorStyle, Position, Style},
|
||||
view::View,
|
||||
text::Style as FontStyle,
|
||||
views::{
|
||||
container, dyn_stack, label, scroll, stack, svg, virtual_stack, Container,
|
||||
Decorators, VirtualDirection, VirtualItemSize,
|
||||
Container, Decorators, container, dyn_stack, label, scroll, stack, svg,
|
||||
virtual_stack,
|
||||
},
|
||||
};
|
||||
use lapce_core::selection::Selection;
|
||||
@@ -23,9 +26,12 @@ use super::{data::FileExplorerData, node::FileNodeVirtualList};
|
||||
use crate::{
|
||||
app::clickable_icon,
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor, icon::LapceIcons},
|
||||
editor_tab::{EditorTabChild, EditorTabData},
|
||||
panel::{kind::PanelKind, position::PanelPosition, view::PanelBuilder},
|
||||
panel::{
|
||||
data::PanelSection, kind::PanelKind, position::PanelPosition,
|
||||
view::PanelBuilder,
|
||||
},
|
||||
plugin::PluginData,
|
||||
source_control::SourceControlData,
|
||||
text_input::TextInputBuilder,
|
||||
@@ -39,30 +45,19 @@ use crate::{
|
||||
///
|
||||
/// The result is always opaque regardless of the transparency of the inputs.
|
||||
fn blend_colors(background: Color, foreground: Color) -> Color {
|
||||
let Color {
|
||||
r: background_r,
|
||||
g: background_g,
|
||||
b: background_b,
|
||||
..
|
||||
} = background;
|
||||
let Color {
|
||||
r: foreground_r,
|
||||
g: foreground_g,
|
||||
b: foreground_b,
|
||||
a,
|
||||
} = foreground;
|
||||
let a: u16 = a.into();
|
||||
|
||||
let background = background.to_rgba8();
|
||||
let foreground = foreground.to_rgba8();
|
||||
let a: u16 = foreground.a.into();
|
||||
let [r, g, b] = [
|
||||
[background_r, foreground_r],
|
||||
[background_g, foreground_g],
|
||||
[background_b, foreground_b],
|
||||
[background.r, foreground.r],
|
||||
[background.g, foreground.g],
|
||||
[background.b, foreground.b],
|
||||
]
|
||||
.map(|x| x.map(u16::from))
|
||||
.map(|[b, f]| (a * f + (255 - a) * b) / 255)
|
||||
.map(|x| x as u8);
|
||||
|
||||
Color { r, g, b, a: 255 }
|
||||
Color::from_rgba8(r, g, b, 255)
|
||||
}
|
||||
|
||||
pub fn file_explorer_panel(
|
||||
@@ -78,16 +73,19 @@ pub fn file_explorer_panel(
|
||||
150.0,
|
||||
container(open_editors_view(window_tab_data.clone()))
|
||||
.style(|s| s.size_full()),
|
||||
move |s| s.apply_if(!config.get().ui.open_editors_visible, Style::hide),
|
||||
window_tab_data.panel.section_open(PanelSection::OpenEditor),
|
||||
move |s| s.apply_if(!config.get().ui.open_editors_visible, |s| s.hide()),
|
||||
)
|
||||
.add(
|
||||
"File Explorer",
|
||||
container(
|
||||
new_file_node_view(data, source_control).style(|s| s.absolute()),
|
||||
)
|
||||
.style(|s| s.size_full().line_height(1.6)),
|
||||
container(file_explorer_view(data, source_control))
|
||||
.style(|s| s.size_full()),
|
||||
window_tab_data
|
||||
.panel
|
||||
.section_open(PanelSection::FileExplorer),
|
||||
)
|
||||
.build()
|
||||
.debug_name("File Explorer Panel")
|
||||
}
|
||||
|
||||
/// Initialize the file explorer's naming (renaming, creating, etc.) editor with the given path.
|
||||
@@ -165,23 +163,57 @@ fn file_node_text_view(
|
||||
let config = data.common.config;
|
||||
let ui_line_height = data.common.ui_line_height;
|
||||
|
||||
let view = match node.kind.clone() {
|
||||
FileNodeViewKind::Path(path) => container(
|
||||
label(move || {
|
||||
path.file_name()
|
||||
.map(|f| f.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.style(move |s| {
|
||||
s.flex_grow(1.0).height(ui_line_height.get()).color(
|
||||
file_node_text_color(
|
||||
config,
|
||||
node.clone(),
|
||||
source_control.clone(),
|
||||
match node.kind.clone() {
|
||||
FileNodeViewKind::Path(path) => {
|
||||
if node.is_root {
|
||||
let file = path.clone();
|
||||
container((
|
||||
label(move || {
|
||||
file.file_name()
|
||||
.map(|f| f.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.style(move |s| {
|
||||
s.height(ui_line_height.get())
|
||||
.color(file_node_text_color(
|
||||
config,
|
||||
node.clone(),
|
||||
source_control.clone(),
|
||||
))
|
||||
.padding_right(5.0)
|
||||
.selectable(false)
|
||||
}),
|
||||
label(move || path.to_string_lossy().to_string()).style(
|
||||
move |s| {
|
||||
s.height(ui_line_height.get())
|
||||
.color(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_FOREGROUND_DIM),
|
||||
)
|
||||
.selectable(false)
|
||||
},
|
||||
),
|
||||
))
|
||||
} else {
|
||||
container(
|
||||
label(move || {
|
||||
path.file_name()
|
||||
.map(|f| f.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.style(move |s| {
|
||||
s.height(ui_line_height.get())
|
||||
.color(file_node_text_color(
|
||||
config,
|
||||
node.clone(),
|
||||
source_control.clone(),
|
||||
))
|
||||
.selectable(false)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
FileNodeViewKind::Renaming { path, err } => {
|
||||
if data.naming.with_untracked(Naming::editor_needs_reset) {
|
||||
initialize_naming_editor_with_path(&data, &path);
|
||||
@@ -203,9 +235,7 @@ fn file_node_text_view(
|
||||
|
||||
file_node_input_view(data, err.clone())
|
||||
}
|
||||
};
|
||||
|
||||
view.style(|s| s.flex_grow(1.0).padding(0.0).margin(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Input used for naming a file/directory
|
||||
@@ -271,9 +301,10 @@ fn file_node_input_view(data: FileExplorerData, err: Option<String>) -> Containe
|
||||
} else {
|
||||
container(text_input_view)
|
||||
}
|
||||
.style(move |s| s.width_full())
|
||||
}
|
||||
|
||||
fn new_file_node_view(
|
||||
fn file_explorer_view(
|
||||
data: FileExplorerData,
|
||||
source_control: SourceControlData,
|
||||
) -> impl View {
|
||||
@@ -281,13 +312,13 @@ fn new_file_node_view(
|
||||
let ui_line_height = data.common.ui_line_height;
|
||||
let config = data.common.config;
|
||||
let naming = data.naming;
|
||||
|
||||
let scroll_to_line = data.scroll_to_line;
|
||||
let select = data.select;
|
||||
let secondary_click_data = data.clone();
|
||||
let scroll_rect = create_rw_signal(Rect::ZERO);
|
||||
|
||||
scroll(
|
||||
virtual_stack(
|
||||
VirtualDirection::Vertical,
|
||||
VirtualItemSize::Fixed(Box::new(move || ui_line_height.get())),
|
||||
move || FileNodeVirtualList::new(root.get(), data.naming.get()),
|
||||
move |node| (node.kind.clone(), node.is_dir, node.open, node.level),
|
||||
move |node| {
|
||||
@@ -366,31 +397,53 @@ fn new_file_node_view(
|
||||
},
|
||||
file_node_text_view(data, node, source_control.clone()),
|
||||
))
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.padding_left((level * 10) as f32)
|
||||
.align_items(AlignItems::Center)
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
.style({
|
||||
let kind = kind.clone();
|
||||
move |s| {
|
||||
s.padding_right(15.0)
|
||||
.min_width_full()
|
||||
.padding_left((level * 10) as f32)
|
||||
.align_items(AlignItems::Center)
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
.apply_if(
|
||||
select.get().map(|x| x == kind).unwrap_or_default(),
|
||||
|x| {
|
||||
x.background(
|
||||
config.get().color(
|
||||
LapceColor::PANEL_CURRENT_BACKGROUND,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
.debug_name("file item");
|
||||
|
||||
// Only handle click events if we are not naming the file node
|
||||
if let FileNodeViewKind::Path(path) = kind {
|
||||
if let FileNodeViewKind::Path(path) = &kind {
|
||||
let click_path = path.clone();
|
||||
let double_click_path = path.clone();
|
||||
let secondary_click_path = path.clone();
|
||||
let aux_click_path = path;
|
||||
view.on_click_stop(move |_| {
|
||||
click_data.click(&click_path);
|
||||
let aux_click_path = path.clone();
|
||||
view.on_click_stop({
|
||||
let kind = kind.clone();
|
||||
move |_| {
|
||||
click_data.click(&click_path, config);
|
||||
select.update(|x| *x = Some(kind.clone()));
|
||||
}
|
||||
})
|
||||
.on_double_click(move |_| {
|
||||
double_click_data.double_click(&double_click_path)
|
||||
.on_double_click({
|
||||
move |_| {
|
||||
double_click_data
|
||||
.double_click(&double_click_path, config)
|
||||
}
|
||||
})
|
||||
.on_secondary_click_stop(move |_| {
|
||||
secondary_click_data.secondary_click(&secondary_click_path);
|
||||
@@ -410,9 +463,10 @@ fn new_file_node_view(
|
||||
}
|
||||
},
|
||||
)
|
||||
.style(|s| s.flex_col().align_items(AlignItems::Stretch).width_full()),
|
||||
.item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.absolute().flex_col().min_width_full()),
|
||||
)
|
||||
.style(|s| s.size_full())
|
||||
.style(|s| s.absolute().size_full().line_height(1.8))
|
||||
.on_secondary_click_stop(move |_| {
|
||||
if let Naming::None = naming.get_untracked() {
|
||||
if let Some(path) = &secondary_click_data.common.workspace.path {
|
||||
@@ -420,6 +474,23 @@ fn new_file_node_view(
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_resize(move |rect| {
|
||||
scroll_rect.set(rect);
|
||||
})
|
||||
.scroll_to(move || {
|
||||
if let Some(line) = scroll_to_line.get() {
|
||||
let line_height = ui_line_height.get_untracked();
|
||||
Some(
|
||||
(
|
||||
0.0,
|
||||
line * line_height - scroll_rect.get_untracked().height() / 2.0,
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn open_editors_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
@@ -479,7 +550,7 @@ fn open_editors_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
},
|
||||
))
|
||||
.style(|s| s.padding_horiz(6.0)),
|
||||
label(move || info.with(|info| info.path.clone())).style(move |s| {
|
||||
label(move || info.with(|info| info.name.clone())).style(move |s| {
|
||||
s.apply_if(
|
||||
!info
|
||||
.with(|info| info.confirmed)
|
||||
@@ -493,6 +564,7 @@ fn open_editors_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
let config = config.get();
|
||||
s.items_center()
|
||||
.width_pct(100.0)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
.apply_if(
|
||||
active_editor_tab.get() == Some(editor_tab_id)
|
||||
&& editor_tab.with(|editor_tab| editor_tab.active)
|
||||
@@ -543,5 +615,6 @@ fn open_editors_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
)
|
||||
.style(|s| s.flex_col().width_pct(100.0)),
|
||||
)
|
||||
.style(|s| s.absolute().size_pct(100.0, 100.0).line_height(1.6))
|
||||
.style(|s| s.absolute().size_full().line_height(1.8))
|
||||
.debug_name("Open Editors")
|
||||
}
|
||||
|
||||
+11
-5
@@ -1,13 +1,16 @@
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use floem::reactive::{RwSignal, Scope};
|
||||
use floem::{
|
||||
prelude::SignalTrack,
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
};
|
||||
use lapce_core::{
|
||||
selection::{SelRegion, Selection},
|
||||
word::WordCursor,
|
||||
};
|
||||
use lapce_xi_rope::{
|
||||
find::{find, is_multiline_regex, CaseMatching},
|
||||
Cursor, Interval, Rope,
|
||||
find::{CaseMatching, find, is_multiline_regex},
|
||||
};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -74,6 +77,8 @@ pub struct Find {
|
||||
pub replace_active: RwSignal<bool>,
|
||||
/// replace editor is focused
|
||||
pub replace_focus: RwSignal<bool>,
|
||||
/// Triggered by changes in the search string
|
||||
pub triggered_by_changes: RwSignal<bool>,
|
||||
}
|
||||
|
||||
impl Find {
|
||||
@@ -87,6 +92,7 @@ impl Find {
|
||||
is_regex: cx.create_rw_signal(false),
|
||||
replace_active: cx.create_rw_signal(false),
|
||||
replace_focus: cx.create_rw_signal(false),
|
||||
triggered_by_changes: cx.create_rw_signal(false),
|
||||
};
|
||||
|
||||
{
|
||||
@@ -160,7 +166,7 @@ impl Find {
|
||||
let is_regex = self.is_regex.get_untracked();
|
||||
|
||||
let search_string_unchanged = self.search_string.with_untracked(|search| {
|
||||
if let Some(ref s) = search {
|
||||
if let Some(s) = search {
|
||||
s.content == search_string && s.regex.is_some() == is_regex
|
||||
} else {
|
||||
false
|
||||
@@ -180,11 +186,11 @@ impl Find {
|
||||
.build()
|
||||
.ok(),
|
||||
};
|
||||
|
||||
self.triggered_by_changes.set(true);
|
||||
self.search_string.set(Some(FindSearchString {
|
||||
content: search_string.to_string(),
|
||||
regex,
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn next(
|
||||
|
||||
+29
-49
@@ -1,16 +1,14 @@
|
||||
use floem::{
|
||||
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout, Weight},
|
||||
id::Id,
|
||||
Renderer, View, ViewId,
|
||||
peniko::{
|
||||
kurbo::{Point, Rect},
|
||||
Color,
|
||||
kurbo::{Point, Rect},
|
||||
},
|
||||
prop_extractor,
|
||||
reactive::create_effect,
|
||||
style::{FontFamily, FontSize, LineHeight, Style, TextColor},
|
||||
taffy::prelude::NodeId,
|
||||
view::{AnyWidget, View, ViewData, Widget},
|
||||
Renderer,
|
||||
text::{Attrs, AttrsList, FamilyOwned, TextLayout, Weight},
|
||||
};
|
||||
|
||||
prop_extractor! {
|
||||
@@ -33,7 +31,7 @@ pub fn focus_text(
|
||||
focus_indices: impl Fn() -> Vec<usize> + 'static,
|
||||
focus_color: impl Fn() -> Color + 'static,
|
||||
) -> FocusText {
|
||||
let id = Id::next();
|
||||
let id = ViewId::new();
|
||||
|
||||
create_effect(move |_| {
|
||||
let new_text = text();
|
||||
@@ -52,10 +50,9 @@ pub fn focus_text(
|
||||
|
||||
FocusText {
|
||||
id,
|
||||
data: ViewData::new(id),
|
||||
text: "".to_string(),
|
||||
text_layout: None,
|
||||
focus_color: Color::default(),
|
||||
focus_color: Color::BLACK,
|
||||
focus_indices: Vec::new(),
|
||||
text_node: None,
|
||||
available_text: None,
|
||||
@@ -66,8 +63,7 @@ pub fn focus_text(
|
||||
}
|
||||
|
||||
pub struct FocusText {
|
||||
id: Id,
|
||||
data: ViewData,
|
||||
id: ViewId,
|
||||
text: String,
|
||||
text_layout: Option<TextLayout>,
|
||||
focus_color: Color,
|
||||
@@ -81,7 +77,8 @@ pub struct FocusText {
|
||||
|
||||
impl FocusText {
|
||||
fn set_text_layout(&mut self) {
|
||||
let mut attrs = Attrs::new().color(self.style.color().unwrap_or_default());
|
||||
let mut attrs =
|
||||
Attrs::new().color(self.style.color().unwrap_or(Color::BLACK));
|
||||
if let Some(font_size) = self.style.font_size() {
|
||||
attrs = attrs.font_size(font_size);
|
||||
}
|
||||
@@ -97,7 +94,7 @@ impl FocusText {
|
||||
attrs = attrs.line_height(line_height);
|
||||
}
|
||||
|
||||
let mut attrs_list = AttrsList::new(attrs);
|
||||
let mut attrs_list = AttrsList::new(attrs.clone());
|
||||
|
||||
for &i_start in &self.focus_indices {
|
||||
let i_end = self
|
||||
@@ -112,18 +109,18 @@ impl FocusText {
|
||||
};
|
||||
attrs_list.add_span(
|
||||
i_start..i_end,
|
||||
attrs.color(self.focus_color).weight(Weight::BOLD),
|
||||
attrs.clone().color(self.focus_color).weight(Weight::BOLD),
|
||||
);
|
||||
}
|
||||
let mut text_layout = TextLayout::new();
|
||||
text_layout.set_text(&self.text, attrs_list);
|
||||
text_layout.set_text(&self.text, attrs_list, None);
|
||||
self.text_layout = Some(text_layout);
|
||||
|
||||
if let Some(new_text) = self.available_text.as_ref() {
|
||||
let new_text_len = new_text.len();
|
||||
|
||||
let mut attrs =
|
||||
Attrs::new().color(self.style.color().unwrap_or_default());
|
||||
Attrs::new().color(self.style.color().unwrap_or(Color::BLACK));
|
||||
if let Some(font_size) = self.style.font_size() {
|
||||
attrs = attrs.font_size(font_size);
|
||||
}
|
||||
@@ -136,7 +133,7 @@ impl FocusText {
|
||||
attrs = attrs.family(font_family);
|
||||
}
|
||||
|
||||
let mut attrs_list = AttrsList::new(attrs);
|
||||
let mut attrs_list = AttrsList::new(attrs.clone());
|
||||
|
||||
for &i_start in &self.focus_indices {
|
||||
if i_start + 3 > new_text_len {
|
||||
@@ -154,41 +151,24 @@ impl FocusText {
|
||||
};
|
||||
attrs_list.add_span(
|
||||
i_start..i_end,
|
||||
attrs.color(self.focus_color).weight(Weight::BOLD),
|
||||
attrs.clone().color(self.focus_color).weight(Weight::BOLD),
|
||||
);
|
||||
}
|
||||
let mut text_layout = TextLayout::new();
|
||||
text_layout.set_text(new_text, attrs_list);
|
||||
text_layout.set_text(new_text, attrs_list, None);
|
||||
self.available_text_layout = Some(text_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for FocusText {
|
||||
fn view_data(&self) -> &ViewData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn view_data_mut(&mut self) -> &mut ViewData {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
fn build(self) -> AnyWidget {
|
||||
Box::new(self)
|
||||
}
|
||||
}
|
||||
impl Widget for FocusText {
|
||||
fn view_data(&self) -> &ViewData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn view_data_mut(&mut self) -> &mut ViewData {
|
||||
&mut self.data
|
||||
fn id(&self) -> ViewId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
cx: &mut floem::context::UpdateCx,
|
||||
_cx: &mut floem::context::UpdateCx,
|
||||
state: Box<dyn std::any::Any>,
|
||||
) {
|
||||
if let Ok(state) = state.downcast() {
|
||||
@@ -204,14 +184,14 @@ impl Widget for FocusText {
|
||||
}
|
||||
}
|
||||
self.set_text_layout();
|
||||
cx.request_layout(self.id());
|
||||
self.id.request_layout();
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&mut self, cx: &mut floem::context::StyleCx<'_>) {
|
||||
fn style_pass(&mut self, cx: &mut floem::context::StyleCx<'_>) {
|
||||
if self.style.read(cx) {
|
||||
self.set_text_layout();
|
||||
cx.app_state_mut().request_layout(self.id());
|
||||
self.id.request_layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +199,7 @@ impl Widget for FocusText {
|
||||
&mut self,
|
||||
cx: &mut floem::context::LayoutCx,
|
||||
) -> floem::taffy::prelude::NodeId {
|
||||
cx.layout_node(self.id, true, |cx| {
|
||||
cx.layout_node(self.id, true, |_cx| {
|
||||
if self.text_layout.is_none() {
|
||||
self.set_text_layout();
|
||||
}
|
||||
@@ -230,22 +210,22 @@ impl Widget for FocusText {
|
||||
let height = size.height as f32;
|
||||
|
||||
if self.text_node.is_none() {
|
||||
self.text_node = Some(cx.new_node());
|
||||
self.text_node = Some(self.id.new_taffy_node());
|
||||
}
|
||||
let text_node = self.text_node.unwrap();
|
||||
|
||||
let style = Style::new().width(width).height(height).to_taffy_style();
|
||||
cx.set_style(text_node, style);
|
||||
self.id.set_taffy_style(text_node, style);
|
||||
vec![text_node]
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_layout(
|
||||
&mut self,
|
||||
cx: &mut floem::context::ComputeLayoutCx,
|
||||
_cx: &mut floem::context::ComputeLayoutCx,
|
||||
) -> Option<Rect> {
|
||||
let text_node = self.text_node.unwrap();
|
||||
let layout = cx.layout(text_node).unwrap();
|
||||
let layout = self.id.taffy_layout(text_node).unwrap_or_default();
|
||||
let text_layout = self.text_layout.as_ref().unwrap();
|
||||
let width = text_layout.size().width as f32;
|
||||
if width > layout.size.width {
|
||||
@@ -254,7 +234,7 @@ impl Widget for FocusText {
|
||||
let mut attrs = Attrs::new().color(
|
||||
self.style
|
||||
.color()
|
||||
.unwrap_or_else(|| Color::rgb8(0xf0, 0xf0, 0xea)),
|
||||
.unwrap_or_else(|| Color::from_rgb8(0xf0, 0xf0, 0xea)),
|
||||
);
|
||||
if let Some(font_size) = self.style.font_size() {
|
||||
attrs = attrs.font_size(font_size);
|
||||
@@ -268,7 +248,7 @@ impl Widget for FocusText {
|
||||
if let Some(font_family) = font_family.as_ref() {
|
||||
attrs = attrs.family(font_family);
|
||||
}
|
||||
dots_text.set_text("...", AttrsList::new(attrs));
|
||||
dots_text.set_text("...", AttrsList::new(attrs), None);
|
||||
|
||||
let dots_width = dots_text.size().width as f32;
|
||||
let width_left = layout.size.width - dots_width;
|
||||
@@ -296,7 +276,7 @@ impl Widget for FocusText {
|
||||
|
||||
fn paint(&mut self, cx: &mut floem::context::PaintCx) {
|
||||
let text_node = self.text_node.unwrap();
|
||||
let location = cx.layout(text_node).unwrap().location;
|
||||
let location = self.id.taffy_layout(text_node).unwrap_or_default().location;
|
||||
let point = Point::new(location.x as f64, location.y as f64);
|
||||
if let Some(text_layout) = self.available_text_layout.as_ref() {
|
||||
cx.draw_text(text_layout, point);
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{ops::Range, path::PathBuf, rc::Rc};
|
||||
use floem::{
|
||||
ext_event::create_ext_action,
|
||||
keyboard::Modifiers,
|
||||
reactive::{Memo, RwSignal, Scope},
|
||||
reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
views::VirtualVector,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
@@ -14,7 +14,7 @@ use lapce_xi_rope::Rope;
|
||||
use crate::{
|
||||
command::{CommandExecuted, CommandKind},
|
||||
editor::EditorData,
|
||||
keypress::{condition::Condition, KeyPressFocus},
|
||||
keypress::{KeyPressFocus, condition::Condition},
|
||||
main_split::MainSplitData,
|
||||
window_tab::CommonData,
|
||||
};
|
||||
@@ -38,7 +38,7 @@ impl SearchMatchData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobalSearchData {
|
||||
pub editor: EditorData,
|
||||
pub search_result: RwSignal<IndexMap<PathBuf, SearchMatchData>>,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::{borrow::Cow, ops::Range, path::PathBuf, str::FromStr};
|
||||
|
||||
use floem::reactive::{batch, RwSignal, Scope};
|
||||
use floem::reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, batch};
|
||||
use lapce_core::{
|
||||
buffer::{
|
||||
rope_text::{RopeText, RopeTextRef},
|
||||
Buffer,
|
||||
rope_text::{RopeText, RopeTextRef},
|
||||
},
|
||||
rope_text_pos::RopeTextPosition,
|
||||
selection::Selection,
|
||||
};
|
||||
|
||||
use lsp_types::InsertTextFormat;
|
||||
|
||||
use crate::{config::LapceConfig, doc::Doc, editor::EditorData, snippet::Snippet};
|
||||
|
||||
+27
-24
@@ -1,26 +1,25 @@
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
View,
|
||||
event::{Event, EventListener},
|
||||
reactive::{
|
||||
create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal,
|
||||
Scope,
|
||||
Memo, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith,
|
||||
create_effect, create_memo, create_rw_signal,
|
||||
},
|
||||
style::CursorStyle,
|
||||
view::View,
|
||||
views::{
|
||||
container, dyn_stack, label, scroll, stack, text, virtual_stack, Decorators,
|
||||
VirtualDirection, VirtualItemSize,
|
||||
Decorators, container, dyn_stack, label, scroll, stack, text, virtual_stack,
|
||||
},
|
||||
};
|
||||
use lapce_core::mode::Modes;
|
||||
|
||||
use crate::{
|
||||
command::LapceCommand,
|
||||
config::{color::LapceColor, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor},
|
||||
keypress::{
|
||||
keymap::{KeyMap, KeyMapPress},
|
||||
KeyPressData,
|
||||
keymap::{KeyMap, KeyMapPress},
|
||||
},
|
||||
main_split::Editors,
|
||||
text_input::TextInputBuilder,
|
||||
@@ -283,7 +282,8 @@ pub fn keymap_view(editors: Editors, common: Rc<CommonData>) -> impl View {
|
||||
container(
|
||||
text_input_view
|
||||
.placeholder(|| "Search Key Bindings".to_string())
|
||||
.keyboard_navigatable()
|
||||
.keyboard_navigable()
|
||||
.request_focus(|| {})
|
||||
.style(move |s| {
|
||||
s.width_pct(100.0)
|
||||
.border_radius(6.0)
|
||||
@@ -346,8 +346,6 @@ pub fn keymap_view(editors: Editors, common: Rc<CommonData>) -> impl View {
|
||||
container(
|
||||
scroll(
|
||||
virtual_stack(
|
||||
VirtualDirection::Vertical,
|
||||
VirtualItemSize::Fixed(Box::new(ui_line_height)),
|
||||
items,
|
||||
|(i, (cmd, keymap)): &(
|
||||
usize,
|
||||
@@ -355,6 +353,7 @@ pub fn keymap_view(editors: Editors, common: Rc<CommonData>) -> impl View {
|
||||
)| { (*i, cmd.kind.str(), keymap.clone()) },
|
||||
view_fn,
|
||||
)
|
||||
.item_size_fixed(ui_line_height)
|
||||
.style(|s| s.flex_col().width_pct(100.0)),
|
||||
)
|
||||
.style(|s| s.absolute().size_pct(100.0, 100.0)),
|
||||
@@ -409,6 +408,7 @@ fn keyboard_picker_view(
|
||||
s.padding_horiz(5.0)
|
||||
.padding_vert(1.0)
|
||||
.margin_right(5.0)
|
||||
.height(ui_line_height.get() as f32)
|
||||
.border(1.0)
|
||||
.border_radius(6.0)
|
||||
.border_color(
|
||||
@@ -423,7 +423,7 @@ fn keyboard_picker_view(
|
||||
.justify_center()
|
||||
.width_pct(100.0)
|
||||
.margin_top(20.0)
|
||||
.height(ui_line_height.get() as f32 + 16.0)
|
||||
.height((ui_line_height.get() as f32) * 1.2)
|
||||
.border(1.0)
|
||||
.border_radius(6.0)
|
||||
.border_color(config.color(LapceColor::LAPCE_BORDER))
|
||||
@@ -500,6 +500,7 @@ fn keyboard_picker_view(
|
||||
.border_color(config.color(LapceColor::LAPCE_BORDER))
|
||||
}),
|
||||
))
|
||||
.on_event_stop(EventListener::PointerDown, |_| {})
|
||||
.style(move |s| {
|
||||
let config = config.get();
|
||||
s.items_center()
|
||||
@@ -512,22 +513,23 @@ fn keyboard_picker_view(
|
||||
.background(config.color(LapceColor::PANEL_BACKGROUND))
|
||||
}),
|
||||
)
|
||||
.keyboard_navigatable()
|
||||
.keyboard_navigable()
|
||||
.on_event_stop(EventListener::KeyDown, move |event| {
|
||||
if let Event::KeyDown(key_event) = event {
|
||||
if let Some(keypress) = KeyPressData::keypress(key_event) {
|
||||
let keypress = keypress.keymap_press();
|
||||
picker.keys.update(|keys| {
|
||||
if let Some((last_key, last_key_confirmed)) = keys.last() {
|
||||
if !*last_key_confirmed && last_key.is_modifiers() {
|
||||
keys.pop();
|
||||
if let Some(keypress) = keypress.keymap_press() {
|
||||
picker.keys.update(|keys| {
|
||||
if let Some((last_key, last_key_confirmed)) = keys.last() {
|
||||
if !*last_key_confirmed && last_key.is_modifiers() {
|
||||
keys.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
if keys.len() == 2 {
|
||||
keys.clear();
|
||||
}
|
||||
keys.push((keypress, false));
|
||||
})
|
||||
if keys.len() == 2 {
|
||||
keys.clear();
|
||||
}
|
||||
keys.push((keypress, false));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -546,7 +548,8 @@ fn keyboard_picker_view(
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.apply_if(picker.keymap.with(|keymap| keymap.is_none()), |s| s.hide())
|
||||
});
|
||||
})
|
||||
.debug_name("keyboard picker");
|
||||
|
||||
let id = view.id();
|
||||
create_effect(move |_| {
|
||||
|
||||
+136
-45
@@ -4,13 +4,13 @@ pub mod keymap;
|
||||
mod loader;
|
||||
mod press;
|
||||
|
||||
use std::{path::PathBuf, rc::Rc, str::FromStr};
|
||||
use std::{path::PathBuf, rc::Rc, str::FromStr, time::SystemTime};
|
||||
|
||||
use anyhow::Result;
|
||||
use floem::{
|
||||
keyboard::{Key, KeyEvent, KeyEventExtModifierSupplement, Modifiers, NamedKey},
|
||||
pointer::PointerInputEvent,
|
||||
reactive::{RwSignal, Scope},
|
||||
pointer::{MouseButton, PointerButton, PointerInputEvent},
|
||||
reactive::{RwSignal, Scope, SignalUpdate, SignalWith},
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
@@ -23,12 +23,13 @@ use self::{
|
||||
loader::KeyMapLoader,
|
||||
};
|
||||
use crate::{
|
||||
command::{lapce_internal_commands, CommandExecuted, CommandKind, LapceCommand},
|
||||
command::{CommandExecuted, CommandKind, LapceCommand, lapce_internal_commands},
|
||||
config::LapceConfig,
|
||||
keypress::{
|
||||
condition::{CheckCondition, Condition},
|
||||
keymap::KeymapMatch,
|
||||
},
|
||||
tracing::*,
|
||||
};
|
||||
|
||||
const DEFAULT_KEYMAPS_COMMON: &str =
|
||||
@@ -38,7 +39,7 @@ const DEFAULT_KEYMAPS_MACOS: &str =
|
||||
const DEFAULT_KEYMAPS_NONMACOS: &str =
|
||||
include_str!("../../defaults/keymaps-nonmacos.toml");
|
||||
|
||||
pub trait KeyPressFocus {
|
||||
pub trait KeyPressFocus: std::fmt::Debug {
|
||||
fn get_mode(&self) -> Mode;
|
||||
|
||||
fn check_condition(&self, condition: Condition) -> bool;
|
||||
@@ -137,10 +138,16 @@ impl<'a> From<&'a PointerInputEvent> for EventRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyPressHandle {
|
||||
pub handled: bool,
|
||||
pub keypress: KeyPress,
|
||||
pub keymatch: KeymapMatch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyPressData {
|
||||
count: RwSignal<Option<usize>>,
|
||||
pending_keypress: RwSignal<Vec<KeyPress>>,
|
||||
pending_keypress: RwSignal<(Vec<KeyPress>, Option<SystemTime>)>,
|
||||
pub commands: Rc<IndexMap<String, LapceCommand>>,
|
||||
pub keymaps: Rc<IndexMap<Vec<KeyMapPress>, Vec<KeyMap>>>,
|
||||
pub command_keymaps: Rc<IndexMap<String, Vec<KeyMap>>>,
|
||||
@@ -154,7 +161,7 @@ impl KeyPressData {
|
||||
Self::get_keymaps(config).unwrap_or((IndexMap::new(), IndexMap::new()));
|
||||
let mut keypress = Self {
|
||||
count: cx.create_rw_signal(None),
|
||||
pending_keypress: cx.create_rw_signal(Vec::new()),
|
||||
pending_keypress: cx.create_rw_signal((Vec::new(), None)),
|
||||
keymaps: Rc::new(keymaps),
|
||||
command_keymaps: Rc::new(command_keymaps),
|
||||
commands: Rc::new(lapce_internal_commands()),
|
||||
@@ -185,7 +192,12 @@ impl KeyPressData {
|
||||
}
|
||||
|
||||
for (_, cmd) in self.commands.iter() {
|
||||
if !self.command_keymaps.contains_key(cmd.kind.str()) {
|
||||
if self
|
||||
.command_keymaps
|
||||
.get(cmd.kind.str())
|
||||
.map(|x| x.is_empty())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
commands_without_keymap.push(cmd.clone());
|
||||
}
|
||||
}
|
||||
@@ -244,7 +256,6 @@ impl KeyPressData {
|
||||
|
||||
pub fn keypress<'a>(event: impl Into<EventRef<'a>>) -> Option<KeyPress> {
|
||||
let event = event.into();
|
||||
tracing::trace!("{event:?}");
|
||||
|
||||
let keypress = match event {
|
||||
EventRef::Keyboard(ev) => KeyPress {
|
||||
@@ -252,6 +263,8 @@ impl KeyPressData {
|
||||
logical: ev.key.logical_key.to_owned(),
|
||||
physical: ev.key.physical_key,
|
||||
key_without_modifiers: ev.key.key_without_modifiers(),
|
||||
location: ev.key.location,
|
||||
repeat: ev.key.repeat,
|
||||
},
|
||||
mods: Self::get_key_modifiers(ev),
|
||||
},
|
||||
@@ -267,58 +280,119 @@ impl KeyPressData {
|
||||
&self,
|
||||
event: impl Into<EventRef<'a>>,
|
||||
focus: &T,
|
||||
) -> bool {
|
||||
) -> KeyPressHandle {
|
||||
let keypress = match Self::keypress(event) {
|
||||
Some(keypress) => keypress,
|
||||
None => return false,
|
||||
None => {
|
||||
return KeyPressHandle {
|
||||
handled: false,
|
||||
keymatch: KeymapMatch::None,
|
||||
keypress: KeyPress {
|
||||
key: KeyInput::Pointer(PointerButton::Mouse(
|
||||
MouseButton::Primary,
|
||||
)),
|
||||
mods: Modifiers::empty(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
let mods = keypress.mods;
|
||||
|
||||
if self.handle_count(focus, &keypress) {
|
||||
return true;
|
||||
return KeyPressHandle {
|
||||
handled: true,
|
||||
keymatch: KeymapMatch::None,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
|
||||
self.pending_keypress.update(|pending_keypress| {
|
||||
pending_keypress.push(keypress.clone());
|
||||
});
|
||||
self.pending_keypress
|
||||
.update(|(pending_keypress, last_time)| {
|
||||
let last_time = last_time.replace(SystemTime::now());
|
||||
if let Some(last_time_val) = last_time {
|
||||
if last_time_val
|
||||
.elapsed()
|
||||
.map(|x| x.as_millis() > 1000)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
pending_keypress.clear();
|
||||
}
|
||||
}
|
||||
pending_keypress.push(keypress.clone());
|
||||
});
|
||||
|
||||
let keymatch = self.pending_keypress.with_untracked(|pending_keypress| {
|
||||
self.match_keymap(pending_keypress, focus)
|
||||
});
|
||||
match keymatch {
|
||||
KeymapMatch::Full(command) => {
|
||||
self.pending_keypress.update(|pending_keypress| {
|
||||
pending_keypress.clear();
|
||||
let keymatch =
|
||||
self.pending_keypress
|
||||
.with_untracked(|(pending_keypress, _)| {
|
||||
self.match_keymap(pending_keypress, focus)
|
||||
});
|
||||
self.handle_keymatch(focus, keymatch, keypress)
|
||||
}
|
||||
|
||||
pub fn handle_keymatch<T: KeyPressFocus + ?Sized>(
|
||||
&self,
|
||||
focus: &T,
|
||||
keymatch: KeymapMatch,
|
||||
keypress: KeyPress,
|
||||
) -> KeyPressHandle {
|
||||
let mods = keypress.mods;
|
||||
match &keymatch {
|
||||
KeymapMatch::Full(command) => {
|
||||
self.pending_keypress
|
||||
.update(|(pending_keypress, last_time)| {
|
||||
last_time.take();
|
||||
pending_keypress.clear();
|
||||
});
|
||||
let count = self.count.try_update(|count| count.take()).unwrap();
|
||||
return self.run_command(&command, count, mods, focus)
|
||||
let handled = self.run_command(command, count, mods, focus)
|
||||
== CommandExecuted::Yes;
|
||||
return KeyPressHandle {
|
||||
handled,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
KeymapMatch::Multiple(commands) => {
|
||||
self.pending_keypress.update(|pending_keypress| {
|
||||
pending_keypress.clear();
|
||||
});
|
||||
self.pending_keypress
|
||||
.update(|(pending_keypress, last_time)| {
|
||||
last_time.take();
|
||||
pending_keypress.clear();
|
||||
});
|
||||
let count = self.count.try_update(|count| count.take()).unwrap();
|
||||
for command in commands {
|
||||
if self.run_command(&command, count, mods, focus)
|
||||
== CommandExecuted::Yes
|
||||
{
|
||||
return true;
|
||||
let handled = self.run_command(command, count, mods, focus)
|
||||
== CommandExecuted::Yes;
|
||||
if handled {
|
||||
return KeyPressHandle {
|
||||
handled,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return KeyPressHandle {
|
||||
handled: false,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
KeymapMatch::Prefix => {
|
||||
// Here pending_keypress contains only a prefix of some keymap, so let's keep
|
||||
// collecting key presses.
|
||||
return true;
|
||||
return KeyPressHandle {
|
||||
handled: true,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
KeymapMatch::None => {
|
||||
self.pending_keypress.update(|pending_keypress| {
|
||||
pending_keypress.clear();
|
||||
});
|
||||
self.pending_keypress
|
||||
.update(|(pending_keypress, last_time)| {
|
||||
pending_keypress.clear();
|
||||
last_time.take();
|
||||
});
|
||||
if focus.get_mode() == Mode::Insert {
|
||||
let old_keypress = keypress.clone();
|
||||
let mut keypress = keypress.clone();
|
||||
keypress.mods.set(Modifiers::SHIFT, false);
|
||||
if let KeymapMatch::Full(command) =
|
||||
@@ -326,8 +400,13 @@ impl KeyPressData {
|
||||
{
|
||||
if let Some(cmd) = self.commands.get(&command) {
|
||||
if let CommandKind::Move(_) = cmd.kind {
|
||||
return focus.run_command(cmd, None, mods)
|
||||
let handled = focus.run_command(cmd, None, mods)
|
||||
== CommandExecuted::Yes;
|
||||
return KeyPressHandle {
|
||||
handled,
|
||||
keymatch,
|
||||
keypress: old_keypress,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,16 +431,28 @@ impl KeyPressData {
|
||||
if let Key::Character(c) = logical {
|
||||
focus.receive_char(c);
|
||||
self.count.set(None);
|
||||
return true;
|
||||
return KeyPressHandle {
|
||||
handled: true,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
} else if let Key::Named(NamedKey::Space) = logical {
|
||||
focus.receive_char(" ");
|
||||
self.count.set(None);
|
||||
return true;
|
||||
return KeyPressHandle {
|
||||
handled: true,
|
||||
keymatch,
|
||||
keypress,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
KeyPressHandle {
|
||||
handled: false,
|
||||
keymatch,
|
||||
keypress,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_modifiers(key_event: &KeyEvent) -> Modifiers {
|
||||
@@ -385,7 +476,7 @@ impl KeyPressData {
|
||||
check: &T,
|
||||
) -> KeymapMatch {
|
||||
let keypresses: Vec<KeyMapPress> =
|
||||
keypresses.iter().map(|k| k.keymap_press()).collect();
|
||||
keypresses.iter().filter_map(|k| k.keymap_press()).collect();
|
||||
let matches: Vec<_> = self
|
||||
.keymaps
|
||||
.get(&keypresses)
|
||||
@@ -483,7 +574,7 @@ impl KeyPressData {
|
||||
let mut loader = KeyMapLoader::new();
|
||||
|
||||
if let Err(err) = loader.load_from_str(DEFAULT_KEYMAPS_COMMON, is_modal) {
|
||||
tracing::error!("Failed to load common defaults: {err}");
|
||||
trace!(TraceLevel::ERROR, "Failed to load common defaults: {err}");
|
||||
}
|
||||
|
||||
let os_keymaps = if std::env::consts::OS == "macos" {
|
||||
@@ -493,13 +584,13 @@ impl KeyPressData {
|
||||
};
|
||||
|
||||
if let Err(err) = loader.load_from_str(os_keymaps, is_modal) {
|
||||
tracing::error!("Failed to load OS defaults: {err}");
|
||||
trace!(TraceLevel::ERROR, "Failed to load OS defaults: {err}");
|
||||
}
|
||||
|
||||
if let Some(path) = Self::file() {
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
if let Err(err) = loader.load_from_str(&content, is_modal) {
|
||||
tracing::warn!("Failed to load from {path:?}: {err}");
|
||||
trace!(TraceLevel::WARN, "Failed to load from {path:?}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl<'a> CheckCondition<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumString, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, EnumString, PartialEq, Eq)]
|
||||
pub enum Condition {
|
||||
#[strum(serialize = "editor_focus")]
|
||||
EditorFocus,
|
||||
@@ -65,6 +65,8 @@ pub enum Condition {
|
||||
RenameFocus,
|
||||
#[strum(serialize = "search_active")]
|
||||
SearchActive,
|
||||
#[strum(serialize = "on_screen_find_active")]
|
||||
OnScreenFindActive,
|
||||
#[strum(serialize = "search_focus")]
|
||||
SearchFocus,
|
||||
#[strum(serialize = "replace_focus")]
|
||||
@@ -77,8 +79,9 @@ mod test {
|
||||
use lapce_core::mode::Mode;
|
||||
|
||||
use super::Condition;
|
||||
use crate::keypress::{condition::CheckCondition, KeyPressData, KeyPressFocus};
|
||||
use crate::keypress::{KeyPressData, KeyPressFocus, condition::CheckCondition};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct MockFocus {
|
||||
accepted_conditions: &'static [Condition],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use floem::keyboard::{Key, NamedKey, PhysicalKey};
|
||||
use floem::keyboard::{Key, KeyLocation, NamedKey, PhysicalKey};
|
||||
|
||||
use super::keymap::KeyMapKey;
|
||||
|
||||
@@ -7,35 +7,68 @@ pub(crate) enum KeyInput {
|
||||
Keyboard {
|
||||
physical: PhysicalKey,
|
||||
logical: Key,
|
||||
location: KeyLocation,
|
||||
key_without_modifiers: Key,
|
||||
repeat: bool,
|
||||
},
|
||||
Pointer(floem::pointer::PointerButton),
|
||||
}
|
||||
|
||||
impl KeyInput {
|
||||
pub fn keymap_key(&self) -> KeyMapKey {
|
||||
match self {
|
||||
pub fn keymap_key(&self) -> Option<KeyMapKey> {
|
||||
if let KeyInput::Keyboard {
|
||||
repeat, logical, ..
|
||||
} = self
|
||||
{
|
||||
if *repeat
|
||||
&& (matches!(
|
||||
logical,
|
||||
Key::Named(NamedKey::Meta)
|
||||
| Key::Named(NamedKey::Shift)
|
||||
| Key::Named(NamedKey::Alt)
|
||||
| Key::Named(NamedKey::Control),
|
||||
))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(match self {
|
||||
KeyInput::Pointer(b) => KeyMapKey::Pointer(*b),
|
||||
KeyInput::Keyboard {
|
||||
physical,
|
||||
key_without_modifiers,
|
||||
logical,
|
||||
location,
|
||||
..
|
||||
} => match key_without_modifiers {
|
||||
Key::Named(_) => {
|
||||
KeyMapKey::Logical(key_without_modifiers.to_owned())
|
||||
}
|
||||
Key::Character(c) => {
|
||||
if c == " " {
|
||||
KeyMapKey::Logical(Key::Named(NamedKey::Space))
|
||||
} else if c.len() == 1 && c.is_ascii() {
|
||||
KeyMapKey::Logical(Key::Character(c.to_lowercase().into()))
|
||||
} else {
|
||||
KeyMapKey::Physical(*physical)
|
||||
} => {
|
||||
#[allow(clippy::single_match)]
|
||||
match location {
|
||||
KeyLocation::Numpad => {
|
||||
return Some(KeyMapKey::Logical(logical.to_owned()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Key::Unidentified(_) => KeyMapKey::Physical(*physical),
|
||||
Key::Dead(_) => KeyMapKey::Physical(*physical),
|
||||
},
|
||||
}
|
||||
|
||||
match key_without_modifiers {
|
||||
Key::Named(_) => {
|
||||
KeyMapKey::Logical(key_without_modifiers.to_owned())
|
||||
}
|
||||
Key::Character(c) => {
|
||||
if c == " " {
|
||||
KeyMapKey::Logical(Key::Named(NamedKey::Space))
|
||||
} else if c.len() == 1 && c.is_ascii() {
|
||||
KeyMapKey::Logical(Key::Character(
|
||||
c.to_lowercase().into(),
|
||||
))
|
||||
} else {
|
||||
KeyMapKey::Physical(*physical)
|
||||
}
|
||||
}
|
||||
Key::Unidentified(_) => KeyMapKey::Physical(*physical),
|
||||
Key::Dead(_) => KeyMapKey::Physical(*physical),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use floem::{
|
||||
keyboard::{Key, KeyCode, Modifiers, NamedKey, PhysicalKey},
|
||||
pointer::PointerButton,
|
||||
pointer::{MouseButton, PointerButton},
|
||||
};
|
||||
use lapce_core::mode::Modes;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(super) enum KeymapMatch {
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum KeymapMatch {
|
||||
Full(String),
|
||||
Multiple(Vec<String>),
|
||||
Prefix,
|
||||
@@ -32,7 +32,7 @@ pub enum KeyMapKey {
|
||||
impl std::hash::Hash for KeyMapKey {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Self::Pointer(btn) => (*btn as u8).hash(state),
|
||||
Self::Pointer(btn) => (btn.mouse_button() as u8).hash(state),
|
||||
Self::Logical(key) => key.hash(state),
|
||||
Self::Physical(physical) => physical.hash(state),
|
||||
}
|
||||
@@ -232,7 +232,7 @@ impl FromStr for KeyMapKey {
|
||||
return Err(anyhow::anyhow!(
|
||||
"unrecognized physical key code {}",
|
||||
&s[1..s.len() - 2]
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
KeyMapKey::Physical(PhysicalKey::Code(code))
|
||||
@@ -317,19 +317,29 @@ impl FromStr for KeyMapKey {
|
||||
impl Display for KeyMapPress {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.mods.contains(Modifiers::CONTROL) {
|
||||
let _ = f.write_str("Ctrl+");
|
||||
if let Err(err) = f.write_str("Ctrl+") {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
if self.mods.contains(Modifiers::ALT) {
|
||||
let _ = f.write_str("Alt+");
|
||||
if let Err(err) = f.write_str("Alt+") {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
if self.mods.contains(Modifiers::ALTGR) {
|
||||
let _ = f.write_str("AltGr+");
|
||||
if let Err(err) = f.write_str("AltGr+") {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
if self.mods.contains(Modifiers::META) {
|
||||
let _ = f.write_str("Meta+");
|
||||
if let Err(err) = f.write_str("Meta+") {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
if self.mods.contains(Modifiers::SHIFT) {
|
||||
let _ = f.write_str("Shift+");
|
||||
if let Err(err) = f.write_str("Shift+") {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
f.write_str(&self.key.to_string())
|
||||
}
|
||||
@@ -686,9 +696,11 @@ impl Display for KeyMapKey {
|
||||
Key::Unidentified(_) => f.write_str("Unidentified"),
|
||||
Key::Dead(_) => f.write_str("dead"),
|
||||
},
|
||||
Self::Pointer(B::Auxiliary) => f.write_str("MouseMiddle"),
|
||||
Self::Pointer(B::X2) => f.write_str("MouseForward"),
|
||||
Self::Pointer(B::X1) => f.write_str("MouseBackward"),
|
||||
Self::Pointer(B::Mouse(MouseButton::Auxiliary)) => {
|
||||
f.write_str("MouseMiddle")
|
||||
}
|
||||
Self::Pointer(B::Mouse(MouseButton::X2)) => f.write_str("MouseForward"),
|
||||
Self::Pointer(B::Mouse(MouseButton::X1)) => f.write_str("MouseBackward"),
|
||||
Self::Pointer(_) => f.write_str("MouseUnimplemented"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use indexmap::IndexMap;
|
||||
use lapce_core::mode::Modes;
|
||||
use tracing::{debug, error};
|
||||
@@ -139,9 +139,8 @@ fn get_modes(toml_keymap: &toml_edit::Table) -> Modes {
|
||||
mod tests {
|
||||
use floem::keyboard::Key;
|
||||
|
||||
use crate::keypress::keymap::KeyMapKey;
|
||||
|
||||
use super::*;
|
||||
use crate::keypress::keymap::KeyMapKey;
|
||||
|
||||
#[test]
|
||||
fn test_keymap() {
|
||||
|
||||
@@ -9,10 +9,10 @@ pub struct KeyPress {
|
||||
}
|
||||
|
||||
impl KeyPress {
|
||||
pub fn keymap_press(&self) -> KeyMapPress {
|
||||
KeyMapPress {
|
||||
key: self.key.keymap_key(),
|
||||
pub fn keymap_press(&self) -> Option<KeyMapPress> {
|
||||
self.key.keymap_key().map(|key| KeyMapPress {
|
||||
key,
|
||||
mods: self.mods,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod about;
|
||||
pub mod alert;
|
||||
pub mod app;
|
||||
pub mod code_action;
|
||||
pub mod code_lens;
|
||||
pub mod command;
|
||||
pub mod completion;
|
||||
pub mod config;
|
||||
@@ -21,6 +22,7 @@ pub mod inline_completion;
|
||||
pub mod keymap;
|
||||
pub mod keypress;
|
||||
pub mod listener;
|
||||
pub mod lsp;
|
||||
pub mod main_split;
|
||||
pub mod markdown;
|
||||
pub mod palette;
|
||||
@@ -36,6 +38,7 @@ pub mod terminal;
|
||||
pub mod text_area;
|
||||
pub mod text_input;
|
||||
pub mod title;
|
||||
pub mod tracing;
|
||||
pub mod update;
|
||||
pub mod wave;
|
||||
pub mod web_link;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use floem::reactive::{RwSignal, Scope};
|
||||
use floem::reactive::{RwSignal, Scope, SignalGet, SignalUpdate};
|
||||
|
||||
/// A signal listener that receives 'events' from the outside and runs the callback.
|
||||
/// This is implemented using effects and normal rw signals. This should be used when it doesn't
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tracing::{Level, event};
|
||||
use url::Url;
|
||||
|
||||
// Rust-analyzer returns paths in the form of "file:///<drive>:/...", which gets parsed into URL
|
||||
// as "/<drive>://" which is then interpreted by PathBuf::new() as a UNIX-like path from root.
|
||||
// This function strips the additional / from the beginning, if the first segment is a drive letter.
|
||||
#[cfg(windows)]
|
||||
pub fn path_from_url(url: &Url) -> PathBuf {
|
||||
use percent_encoding::percent_decode_str;
|
||||
|
||||
event!(Level::DEBUG, "Converting `{:?}` to path", url);
|
||||
|
||||
if let Ok(path) = url.to_file_path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
let path = url.path();
|
||||
|
||||
let path = if path.contains('%') {
|
||||
percent_decode_str(path)
|
||||
.decode_utf8()
|
||||
.unwrap_or(std::borrow::Cow::from(path))
|
||||
} else {
|
||||
std::borrow::Cow::from(path)
|
||||
};
|
||||
|
||||
if let Some(path) = path.strip_prefix('/') {
|
||||
event!(Level::DEBUG, "Found `/` prefix");
|
||||
if let Some((maybe_drive_letter, path_second_part)) =
|
||||
path.split_once(['/', '\\'])
|
||||
{
|
||||
event!(Level::DEBUG, maybe_drive_letter);
|
||||
event!(Level::DEBUG, path_second_part);
|
||||
|
||||
let b = maybe_drive_letter.as_bytes();
|
||||
|
||||
if !b.is_empty() && !b[0].is_ascii_alphabetic() {
|
||||
event!(Level::ERROR, "First byte is not ascii alphabetic: {b:?}");
|
||||
}
|
||||
|
||||
match maybe_drive_letter.len() {
|
||||
2 => match maybe_drive_letter.chars().nth(1) {
|
||||
Some(':') => {
|
||||
event!(Level::DEBUG, "Returning path `{:?}`", path);
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
v => {
|
||||
event!(
|
||||
Level::ERROR,
|
||||
"Unhandled 'maybe_drive_letter' chars: {v:?}"
|
||||
);
|
||||
}
|
||||
},
|
||||
4 => {
|
||||
if maybe_drive_letter.contains("%3A") {
|
||||
let path = path.replace("%3A", ":");
|
||||
event!(Level::DEBUG, "Returning path `{:?}`", path);
|
||||
return PathBuf::from(path);
|
||||
} else {
|
||||
event!(
|
||||
Level::ERROR,
|
||||
"Unhandled 'maybe_drive_letter' pattern: {maybe_drive_letter:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
v => {
|
||||
event!(
|
||||
Level::ERROR,
|
||||
"Unhandled 'maybe_drive_letter' length: {v}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event!(Level::DEBUG, "Returning unmodified path `{:?}`", path);
|
||||
PathBuf::from(path.into_owned())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn path_from_url(url: &Url) -> PathBuf {
|
||||
event!(Level::DEBUG, "Converting `{:?}` to path", url);
|
||||
url.to_file_path().unwrap_or_else(|_| {
|
||||
let path = url.path();
|
||||
if let Ok(path) = percent_encoding::percent_decode_str(path).decode_utf8() {
|
||||
return PathBuf::from(path.into_owned());
|
||||
}
|
||||
PathBuf::from(path)
|
||||
})
|
||||
}
|
||||
+256
-64
@@ -10,7 +10,7 @@ use floem::{
|
||||
file::{FileDialogOptions, FileInfo},
|
||||
keyboard::Modifiers,
|
||||
peniko::kurbo::{Point, Rect, Vec2},
|
||||
reactive::{Memo, RwSignal, Scope},
|
||||
reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
views::editor::id::EditorId,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@@ -20,25 +20,28 @@ use lapce_core::{
|
||||
};
|
||||
use lapce_rpc::{
|
||||
buffer::BufferId,
|
||||
core::FileChanged,
|
||||
plugin::{PluginId, VoltID},
|
||||
proxy::ProxyResponse,
|
||||
};
|
||||
use lapce_xi_rope::{spans::SpansBuilder, Rope};
|
||||
use lapce_xi_rope::{Rope, spans::SpansBuilder};
|
||||
use lsp_types::{
|
||||
CodeAction, CodeActionOrCommand, DiagnosticSeverity, DocumentChangeOperation,
|
||||
DocumentChanges, OneOf, Position, TextEdit, Url, WorkspaceEdit,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use serde_json::Value;
|
||||
use tracing::{Level, event};
|
||||
|
||||
use crate::{
|
||||
alert::AlertButton,
|
||||
code_lens::CodeLensData,
|
||||
command::InternalCommand,
|
||||
doc::{DiagnosticData, Doc, DocContent, DocHistory, EditorDiagnostic},
|
||||
editor::{
|
||||
EditorData,
|
||||
diff::DiffEditorData,
|
||||
location::{EditorLocation, EditorPosition},
|
||||
EditorData,
|
||||
},
|
||||
editor_tab::{
|
||||
EditorTabChild, EditorTabChildSource, EditorTabData, EditorTabInfo,
|
||||
@@ -47,7 +50,8 @@ use crate::{
|
||||
DiffEditorId, EditorTabId, KeymapId, SettingsId, SplitId,
|
||||
ThemeColorSettingsId, VoltViewId,
|
||||
},
|
||||
keypress::{EventRef, KeyPressData},
|
||||
keypress::{EventRef, KeyPressData, KeyPressHandle},
|
||||
panel::implementation_view::ReferencesRoot,
|
||||
window_tab::{CommonData, Focus, WindowTabData},
|
||||
};
|
||||
|
||||
@@ -203,15 +207,14 @@ impl SplitContentInfo {
|
||||
|
||||
impl SplitData {
|
||||
pub fn split_info(&self, data: &WindowTabData) -> SplitInfo {
|
||||
let info = SplitInfo {
|
||||
SplitInfo {
|
||||
direction: self.direction,
|
||||
children: self
|
||||
.children
|
||||
.iter()
|
||||
.map(|(_, child)| child.content_info(data))
|
||||
.collect(),
|
||||
};
|
||||
info
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor_tab_index(&self, editor_tab_id: EditorTabId) -> Option<usize> {
|
||||
@@ -233,13 +236,13 @@ impl Editors {
|
||||
Self(cx.create_rw_signal(im::HashMap::new()))
|
||||
}
|
||||
|
||||
/// Add an editor to the editors.
|
||||
/// Add an editor to the editors.
|
||||
/// Returns the id of the editor.
|
||||
pub fn insert(&self, editor: EditorData) -> EditorId {
|
||||
let id = editor.id();
|
||||
self.0.update(|editors| {
|
||||
if editors.insert(id, editor).is_some() {
|
||||
warn!("Inserted EditorId that already exists");
|
||||
event!(Level::WARN, "Inserted EditorId that already exists");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -338,6 +341,23 @@ impl Editors {
|
||||
self.0.try_update(|editors| editors.remove(&id)).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_editor_id_by_path(&self, path: &Path) -> Option<EditorId> {
|
||||
self.0.with_untracked(|x| {
|
||||
for (id, data) in x {
|
||||
if data.doc().content.with_untracked(|x| {
|
||||
if let Some(doc_path) = x.path() {
|
||||
doc_path == path
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Some(*id);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contains_untracked(&self, id: EditorId) -> bool {
|
||||
self.0.with_untracked(|editors| editors.contains_key(&id))
|
||||
}
|
||||
@@ -379,15 +399,26 @@ pub struct MainSplitData {
|
||||
pub docs: RwSignal<im::HashMap<PathBuf, Rc<Doc>>>,
|
||||
pub scratch_docs: RwSignal<im::HashMap<String, Rc<Doc>>>,
|
||||
pub diagnostics: RwSignal<im::HashMap<PathBuf, DiagnosticData>>,
|
||||
pub references: RwSignal<ReferencesRoot>,
|
||||
pub implementations: RwSignal<crate::panel::implementation_view::ReferencesRoot>,
|
||||
pub active_editor: Memo<Option<EditorData>>,
|
||||
pub find_editor: EditorData,
|
||||
pub replace_editor: EditorData,
|
||||
pub locations: RwSignal<im::Vector<EditorLocation>>,
|
||||
pub current_location: RwSignal<usize>,
|
||||
pub width: RwSignal<f64>,
|
||||
pub code_lens: RwSignal<CodeLensData>,
|
||||
pub common: Rc<CommonData>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MainSplitData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MainSplitData")
|
||||
.field("root_split", &self.root_split)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl MainSplitData {
|
||||
pub fn new(cx: Scope, common: Rc<CommonData>) -> Self {
|
||||
let splits = cx.create_rw_signal(im::HashMap::new());
|
||||
@@ -402,6 +433,10 @@ impl MainSplitData {
|
||||
cx.create_rw_signal(im::HashMap::new());
|
||||
let scratch_docs = cx.create_rw_signal(im::HashMap::new());
|
||||
let locations = cx.create_rw_signal(im::Vector::new());
|
||||
let references = cx.create_rw_signal(ReferencesRoot::default());
|
||||
let implementations = cx.create_rw_signal(
|
||||
crate::panel::implementation_view::ReferencesRoot::default(),
|
||||
);
|
||||
let current_location = cx.create_rw_signal(0);
|
||||
let diagnostics = cx.create_rw_signal(im::HashMap::new());
|
||||
let find_editor = editors.make_local(cx, common.clone());
|
||||
@@ -459,7 +494,10 @@ impl MainSplitData {
|
||||
locations,
|
||||
current_location,
|
||||
width: cx.create_rw_signal(0.0),
|
||||
code_lens: cx.create_rw_signal(CodeLensData::new(common.clone())),
|
||||
common,
|
||||
references,
|
||||
implementations,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,7 +505,7 @@ impl MainSplitData {
|
||||
&self,
|
||||
event: impl Into<EventRef<'a>>,
|
||||
keypress: &KeyPressData,
|
||||
) -> Option<bool> {
|
||||
) -> Option<KeyPressHandle> {
|
||||
let active_editor_tab = self.active_editor_tab.get_untracked()?;
|
||||
let editor_tab = self.editor_tabs.with_untracked(|editor_tabs| {
|
||||
editor_tabs.get(&active_editor_tab).copied()
|
||||
@@ -478,9 +516,9 @@ impl MainSplitData {
|
||||
match child {
|
||||
EditorTabChild::Editor(editor_id) => {
|
||||
let editor = self.editors.editor_untracked(editor_id)?;
|
||||
let proccesed = keypress.key_down(event, &editor);
|
||||
let handle = keypress.key_down(event, &editor);
|
||||
editor.get_code_actions();
|
||||
Some(proccesed)
|
||||
Some(handle)
|
||||
}
|
||||
EditorTabChild::DiffEditor(diff_editor_id) => {
|
||||
let diff_editor =
|
||||
@@ -492,9 +530,9 @@ impl MainSplitData {
|
||||
} else {
|
||||
&diff_editor.left
|
||||
};
|
||||
let processed = keypress.key_down(event, editor);
|
||||
let handle = keypress.key_down(event, editor);
|
||||
editor.get_code_actions();
|
||||
Some(processed)
|
||||
Some(handle)
|
||||
}
|
||||
EditorTabChild::Settings(_) => None,
|
||||
EditorTabChild::ThemeColorSettings(_) => None,
|
||||
@@ -575,7 +613,11 @@ impl MainSplitData {
|
||||
self.go_to_location(location, edits);
|
||||
}
|
||||
|
||||
pub fn get_doc(&self, path: PathBuf) -> (Rc<Doc>, bool) {
|
||||
pub fn get_doc(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
unsaved: Option<String>,
|
||||
) -> (Rc<Doc>, bool) {
|
||||
let cx = self.scope;
|
||||
let doc = self.docs.with_untracked(|docs| docs.get(&path).cloned());
|
||||
if let Some(doc) = doc {
|
||||
@@ -611,6 +653,8 @@ impl MainSplitData {
|
||||
*read_only = true;
|
||||
}
|
||||
});
|
||||
} else if let Some(unsaved) = unsaved {
|
||||
local_doc.reload(Rope::from(unsaved), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -621,7 +665,9 @@ impl MainSplitData {
|
||||
send(result);
|
||||
});
|
||||
}
|
||||
|
||||
doc.get_code_lens();
|
||||
doc.get_folding_range();
|
||||
doc.get_document_symbol();
|
||||
(doc, true)
|
||||
}
|
||||
}
|
||||
@@ -635,7 +681,7 @@ impl MainSplitData {
|
||||
self.common.focus.set(Focus::Workbench);
|
||||
}
|
||||
let path = location.path.clone();
|
||||
let (doc, new_doc) = self.get_doc(path.clone());
|
||||
let (doc, new_doc) = self.get_doc(path.clone(), None);
|
||||
|
||||
let child = self.get_editor_tab_child(
|
||||
EditorTabChildSource::Editor { path, doc },
|
||||
@@ -650,7 +696,7 @@ impl MainSplitData {
|
||||
}
|
||||
|
||||
pub fn open_file_changes(&self, path: PathBuf) {
|
||||
let (right, _) = self.get_doc(path.clone());
|
||||
let (right, _) = self.get_doc(path.clone(), None);
|
||||
let left = Doc::new_history(
|
||||
self.scope,
|
||||
DocContent::History(DocHistory {
|
||||
@@ -683,7 +729,8 @@ impl MainSplitData {
|
||||
}
|
||||
|
||||
pub fn open_diff_files(&self, left_path: PathBuf, right_path: PathBuf) {
|
||||
let [left, right] = [left_path, right_path].map(|path| self.get_doc(path).0);
|
||||
let [left, right] =
|
||||
[left_path, right_path].map(|path| self.get_doc(path, None).0);
|
||||
|
||||
self.get_editor_tab_child(
|
||||
EditorTabChildSource::DiffEditor { left, right },
|
||||
@@ -1676,8 +1723,8 @@ impl MainSplitData {
|
||||
let splits = self.splits.get_untracked();
|
||||
let split = splits.get(&split_id).copied()?;
|
||||
|
||||
let split_chilren = split.with_untracked(|split| split.children.clone());
|
||||
let content = split_chilren.first()?;
|
||||
let split_children = split.with_untracked(|split| split.children.clone());
|
||||
let content = split_children.first()?;
|
||||
self.split_content_focus(&content.1);
|
||||
|
||||
Some(())
|
||||
@@ -1902,6 +1949,60 @@ impl MainSplitData {
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn editor_tab_child_close_by_kind(
|
||||
&self,
|
||||
editor_tab_id: EditorTabId,
|
||||
child: EditorTabChild,
|
||||
kind: TabCloseKind,
|
||||
) -> Option<()> {
|
||||
let tabs_to_close: Vec<EditorTabChild> = {
|
||||
let editor_tabs = self.editor_tabs.get_untracked();
|
||||
|
||||
let editor_tab = editor_tabs.get(&editor_tab_id).copied()?;
|
||||
let editor_tab = editor_tab.get_untracked();
|
||||
match kind {
|
||||
TabCloseKind::CloseOther => editor_tab
|
||||
.children
|
||||
.iter()
|
||||
.filter_map(|x| {
|
||||
if x.2 != child {
|
||||
Some(x.2.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
TabCloseKind::CloseToLeft => {
|
||||
let mut tabs_to_close = Vec::new();
|
||||
for child_tab in &editor_tab.children {
|
||||
if child_tab.2 != child {
|
||||
tabs_to_close.push(child_tab.2.clone());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tabs_to_close
|
||||
}
|
||||
TabCloseKind::CloseToRight => {
|
||||
let mut tabs_to_close = Vec::new();
|
||||
let mut add_to_tabs = false;
|
||||
for child_tab in &editor_tab.children {
|
||||
if child_tab.2 != child && add_to_tabs {
|
||||
tabs_to_close.push(child_tab.2.clone());
|
||||
} else {
|
||||
add_to_tabs = true;
|
||||
}
|
||||
}
|
||||
tabs_to_close
|
||||
}
|
||||
}
|
||||
};
|
||||
for child_tab in tabs_to_close {
|
||||
self.editor_tab_child_close(editor_tab_id, child_tab, false);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn editor_tab_child_close(
|
||||
&self,
|
||||
editor_tab_id: EditorTabId,
|
||||
@@ -1926,7 +2027,7 @@ impl MainSplitData {
|
||||
let doc = doc.clone();
|
||||
internal_command.send(InternalCommand::HideAlert);
|
||||
save_as(
|
||||
FileDialogOptions::new(),
|
||||
FileDialogOptions::new().title("Save File"),
|
||||
move |file: Option<FileInfo>| {
|
||||
let main_split = main_split.clone();
|
||||
let child = child.clone();
|
||||
@@ -1992,8 +2093,8 @@ impl MainSplitData {
|
||||
.internal_command
|
||||
.send(InternalCommand::ShowAlert {
|
||||
title: format!(
|
||||
"Do you want to save the changes you made to {name}?"
|
||||
),
|
||||
"Do you want to save the changes you made to {name}?"
|
||||
),
|
||||
msg: "Your changes will be lost if you don't save them."
|
||||
.to_string(),
|
||||
buttons: vec![
|
||||
@@ -2045,6 +2146,8 @@ impl MainSplitData {
|
||||
.unwrap();
|
||||
if let Some(diff_editor) = removed_diff_editor {
|
||||
diff_editor.right.save_doc_position();
|
||||
self.editors.remove(diff_editor.right.editor.id());
|
||||
self.editors.remove(diff_editor.left.editor.id());
|
||||
}
|
||||
}
|
||||
EditorTabChild::Settings(_) => {}
|
||||
@@ -2081,7 +2184,12 @@ impl MainSplitData {
|
||||
|
||||
pub fn run_code_action(&self, plugin_id: PluginId, action: CodeActionOrCommand) {
|
||||
match action {
|
||||
CodeActionOrCommand::Command(_) => {}
|
||||
CodeActionOrCommand::Command(command) => {
|
||||
self.run_code_lens(
|
||||
&command.command,
|
||||
command.arguments.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
CodeActionOrCommand::CodeAction(action) => {
|
||||
if let Some(edit) = action.edit.as_ref() {
|
||||
self.apply_workspace_edit(edit);
|
||||
@@ -2092,6 +2200,10 @@ impl MainSplitData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_code_lens(&self, command: &str, args: Vec<Value>) {
|
||||
self.code_lens.get_untracked().run(command, args);
|
||||
}
|
||||
|
||||
/// Resolve a code action and apply its held workspace edit
|
||||
fn resolve_code_action(&self, plugin_id: PluginId, action: CodeAction) {
|
||||
let main_split = self.clone();
|
||||
@@ -2243,14 +2355,44 @@ impl MainSplitData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file_changed(&self, path: &Path, content: &str) {
|
||||
let doc = self.docs.with_untracked(|docs| docs.get(path).cloned());
|
||||
let doc = match doc {
|
||||
Some(doc) => doc,
|
||||
None => return,
|
||||
};
|
||||
|
||||
doc.handle_file_changed(Rope::from(content));
|
||||
pub fn open_file_changed(&self, path: &Path, content: &FileChanged) {
|
||||
tracing::debug!("open_file_changed {:?}", path);
|
||||
match content {
|
||||
FileChanged::Change(content) => {
|
||||
let doc = self.docs.with_untracked(|docs| docs.get(path).cloned());
|
||||
let doc = match doc {
|
||||
Some(doc) => doc,
|
||||
None => return,
|
||||
};
|
||||
doc.handle_file_changed(Rope::from(content));
|
||||
}
|
||||
FileChanged::Delete => {
|
||||
if self.docs.with_untracked(|x| x.get(path).is_none()) {
|
||||
return;
|
||||
}
|
||||
let Some(editor_id) = self.editors.get_editor_id_by_path(path)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let id = editor_id.to_raw();
|
||||
if let Some(tab_id) = self.editor_tabs.with_untracked(|x| {
|
||||
for (tab_id, tab_data) in x {
|
||||
if tab_data.with_untracked(|x| {
|
||||
x.children.iter().any(|(_, _, child)| child.id() == id)
|
||||
}) {
|
||||
return Some(*tab_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}) {
|
||||
self.editor_tab_close(tab_id);
|
||||
}
|
||||
self.editors.remove(editor_id);
|
||||
self.docs.update(|x| {
|
||||
x.remove(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_find_pattern(&self, pattern: Option<String>) {
|
||||
@@ -2304,7 +2446,11 @@ impl MainSplitData {
|
||||
let path = path.clone();
|
||||
create_ext_action(self.scope, move |result| {
|
||||
if let Err(err) = result {
|
||||
warn!("Failed to save as a file: {:?}", err);
|
||||
event!(
|
||||
Level::WARN,
|
||||
"Failed to save as a file: {:?}",
|
||||
err
|
||||
);
|
||||
} else {
|
||||
let syntax = Syntax::init(&path);
|
||||
doc.content.set(DocContent::File {
|
||||
@@ -2355,7 +2501,11 @@ impl MainSplitData {
|
||||
let path = path.clone();
|
||||
create_ext_action(self.scope, move |result| {
|
||||
if let Err(err) = result {
|
||||
warn!("Failed to save as a file: {:?}", err);
|
||||
event!(
|
||||
Level::WARN,
|
||||
"Failed to save as a file: {:?}",
|
||||
err
|
||||
);
|
||||
} else {
|
||||
let syntax = Syntax::init(&path);
|
||||
doc.content.set(DocContent::File {
|
||||
@@ -2439,38 +2589,44 @@ impl MainSplitData {
|
||||
|
||||
pub fn save_scratch_doc(&self, doc: Rc<Doc>) {
|
||||
let main_split = self.clone();
|
||||
save_as(FileDialogOptions::new(), move |file: Option<FileInfo>| {
|
||||
if let Some(mut file) = file {
|
||||
main_split.save_as(
|
||||
doc.clone(),
|
||||
if let Some(path) = file.path.pop() {
|
||||
path
|
||||
} else {
|
||||
tracing::error!("No path");
|
||||
return;
|
||||
},
|
||||
move || {},
|
||||
);
|
||||
}
|
||||
});
|
||||
save_as(
|
||||
FileDialogOptions::new().title("Save File"),
|
||||
move |file: Option<FileInfo>| {
|
||||
if let Some(mut file) = file {
|
||||
main_split.save_as(
|
||||
doc.clone(),
|
||||
if let Some(path) = file.path.pop() {
|
||||
path
|
||||
} else {
|
||||
tracing::error!("No path");
|
||||
return;
|
||||
},
|
||||
move || {},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn save_scratch_doc2(&self, doc: Rc<Doc>) {
|
||||
let main_split = self.clone();
|
||||
save_as(FileDialogOptions::new(), move |file: Option<FileInfo>| {
|
||||
if let Some(mut file) = file {
|
||||
main_split.save_as2(
|
||||
doc.clone(),
|
||||
if let Some(path) = file.path.pop() {
|
||||
path
|
||||
} else {
|
||||
tracing::error!("No path");
|
||||
return;
|
||||
},
|
||||
move || {},
|
||||
);
|
||||
}
|
||||
});
|
||||
save_as(
|
||||
FileDialogOptions::new().title("Save File"),
|
||||
move |file: Option<FileInfo>| {
|
||||
if let Some(mut file) = file {
|
||||
main_split.save_as2(
|
||||
doc.clone(),
|
||||
if let Some(path) = file.path.pop() {
|
||||
path
|
||||
} else {
|
||||
tracing::error!("No path");
|
||||
return;
|
||||
},
|
||||
move || {},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn move_editor_tab_child(
|
||||
@@ -2809,6 +2965,35 @@ impl MainSplitData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_env(&self) {
|
||||
let child = self.new_file();
|
||||
if let EditorTabChild::Editor(id) = child {
|
||||
if let Some(editor) = self.editors.editor_untracked(id) {
|
||||
let doc = editor.doc();
|
||||
doc.reload(
|
||||
Rope::from(
|
||||
std::env::vars().map(|(k, v)| format!("{k}={v}")).join("\n"),
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_active_editor(&self) -> Option<EditorData> {
|
||||
let active_editor_tab = self.active_editor_tab.get()?;
|
||||
let editor_tabs = self.editor_tabs;
|
||||
let editor_tab = editor_tabs
|
||||
.with(|editor_tabs| editor_tabs.get(&active_editor_tab).copied())?;
|
||||
let (_, _, child) = editor_tab.with(|editor_tab| {
|
||||
editor_tab.children.get(editor_tab.active).cloned()
|
||||
})?;
|
||||
match child {
|
||||
EditorTabChild::Editor(editor_id) => self.editors.editor(editor_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_edits(edit: &WorkspaceEdit) -> Option<HashMap<Url, Vec<TextEdit>>> {
|
||||
@@ -2911,3 +3096,10 @@ fn next_in_file_errors_offset(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TabCloseKind {
|
||||
CloseOther,
|
||||
CloseToLeft,
|
||||
CloseToRight,
|
||||
}
|
||||
|
||||
+17
-13
@@ -1,4 +1,4 @@
|
||||
use floem::cosmic_text::{
|
||||
use floem::text::{
|
||||
Attrs, AttrsList, FamilyOwned, LineHeightValue, Style, TextLayout, Weight,
|
||||
};
|
||||
use lapce_core::{language::LapceLanguage, syntax::Syntax};
|
||||
@@ -7,7 +7,7 @@ use lsp_types::MarkedString;
|
||||
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::config::{color::LapceColor, LapceConfig};
|
||||
use crate::config::{LapceConfig, color::LapceColor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MarkdownContent {
|
||||
@@ -31,7 +31,7 @@ pub fn parse_markdown(
|
||||
.color(config.color(LapceColor::EDITOR_FOREGROUND))
|
||||
.font_size(config.ui.font_size() as f32)
|
||||
.line_height(LineHeightValue::Normal(line_height as f32));
|
||||
let mut attr_list = AttrsList::new(default_attrs);
|
||||
let mut attr_list = AttrsList::new(default_attrs.clone());
|
||||
|
||||
let mut builder_dirty = false;
|
||||
|
||||
@@ -72,7 +72,7 @@ pub fn parse_markdown(
|
||||
}
|
||||
|
||||
if let Some(attrs) = attribute_for_tag(
|
||||
default_attrs,
|
||||
default_attrs.clone(),
|
||||
&tag,
|
||||
&code_font_family,
|
||||
config,
|
||||
@@ -96,7 +96,7 @@ pub fn parse_markdown(
|
||||
|
||||
highlight_as_code(
|
||||
&mut attr_list,
|
||||
default_attrs.family(&code_font_family),
|
||||
default_attrs.clone().family(&code_font_family),
|
||||
language,
|
||||
&last_text,
|
||||
start_offset,
|
||||
@@ -115,9 +115,9 @@ pub fn parse_markdown(
|
||||
|
||||
if builder_dirty {
|
||||
let mut text_layout = TextLayout::new();
|
||||
text_layout.set_text(¤t_text, attr_list);
|
||||
text_layout.set_text(¤t_text, attr_list, None);
|
||||
res.push(MarkdownContent::Text(text_layout));
|
||||
attr_list = AttrsList::new(default_attrs);
|
||||
attr_list = AttrsList::new(default_attrs.clone());
|
||||
current_text.clear();
|
||||
pos = 0;
|
||||
builder_dirty = false;
|
||||
@@ -151,7 +151,7 @@ pub fn parse_markdown(
|
||||
Event::Code(text) => {
|
||||
attr_list.add_span(
|
||||
pos..pos + text.len(),
|
||||
default_attrs.family(&code_font_family),
|
||||
default_attrs.clone().family(&code_font_family),
|
||||
);
|
||||
current_text.push_str(&text);
|
||||
pos += text.len();
|
||||
@@ -163,6 +163,7 @@ pub fn parse_markdown(
|
||||
attr_list.add_span(
|
||||
pos..pos + text.len(),
|
||||
default_attrs
|
||||
.clone()
|
||||
.family(&code_font_family)
|
||||
.color(config.color(LapceColor::MARKDOWN_BLOCKQUOTE)),
|
||||
);
|
||||
@@ -184,12 +185,14 @@ pub fn parse_markdown(
|
||||
Event::FootnoteReference(_text) => {}
|
||||
Event::TaskListMarker(_text) => {}
|
||||
Event::InlineHtml(_) => {} // TODO(panekj): Implement
|
||||
Event::InlineMath(_) => {} // TODO(panekj): Implement
|
||||
Event::DisplayMath(_) => {} // TODO(panekj): Implement
|
||||
}
|
||||
}
|
||||
|
||||
if builder_dirty {
|
||||
let mut text_layout = TextLayout::new();
|
||||
text_layout.set_text(¤t_text, attr_list);
|
||||
text_layout.set_text(¤t_text, attr_list, None);
|
||||
res.push(MarkdownContent::Text(text_layout));
|
||||
}
|
||||
|
||||
@@ -227,7 +230,7 @@ fn attribute_for_tag<'a>(
|
||||
.weight(Weight::BOLD),
|
||||
)
|
||||
}
|
||||
Tag::BlockQuote => Some(
|
||||
Tag::BlockQuote(_block_quote) => Some(
|
||||
default_attrs
|
||||
.style(Style::Italic)
|
||||
.color(config.color(LapceColor::MARKDOWN_BLOCKQUOTE)),
|
||||
@@ -296,7 +299,7 @@ pub fn highlight_as_code(
|
||||
{
|
||||
attr_list.add_span(
|
||||
start_offset + range.start..start_offset + range.end,
|
||||
default_attrs.color(color),
|
||||
default_attrs.clone().color(color),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -308,14 +311,14 @@ pub fn from_marked_string(
|
||||
config: &LapceConfig,
|
||||
) -> Vec<MarkdownContent> {
|
||||
match text {
|
||||
MarkedString::String(text) => parse_markdown(&text, 1.5, config),
|
||||
MarkedString::String(text) => parse_markdown(&text, 1.8, config),
|
||||
// This is a short version of a code block
|
||||
MarkedString::LanguageString(code) => {
|
||||
// TODO: We could simply construct the MarkdownText directly
|
||||
// Simply construct the string as if it was written directly
|
||||
parse_markdown(
|
||||
&format!("```{}\n{}\n```", code.language, code.value),
|
||||
1.5,
|
||||
1.8,
|
||||
config,
|
||||
)
|
||||
}
|
||||
@@ -335,6 +338,7 @@ pub fn from_plaintext(
|
||||
.font_size(config.ui.font_size() as f32)
|
||||
.line_height(LineHeightValue::Normal(line_height as f32)),
|
||||
),
|
||||
None,
|
||||
);
|
||||
vec![MarkdownContent::Text(text_layout)]
|
||||
}
|
||||
|
||||
+145
-75
@@ -4,19 +4,23 @@ use std::{
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
mpsc::{Receiver, Sender, TryRecvError, channel},
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::{Receiver, Sender, TryRecvError};
|
||||
use floem::{
|
||||
ext_event::{create_ext_action, create_signal_from_channel},
|
||||
keyboard::Modifiers,
|
||||
reactive::{use_context, ReadSignal, RwSignal, Scope},
|
||||
reactive::{
|
||||
ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith,
|
||||
use_context,
|
||||
},
|
||||
};
|
||||
use im::Vector;
|
||||
use itertools::Itertools;
|
||||
use lapce_core::{
|
||||
buffer::rope_text::RopeText, command::FocusCommand, language::LapceLanguage,
|
||||
@@ -25,7 +29,7 @@ use lapce_core::{
|
||||
};
|
||||
use lapce_rpc::proxy::ProxyResponse;
|
||||
use lapce_xi_rope::Rope;
|
||||
use lsp_types::DocumentSymbolResponse;
|
||||
use lsp_types::{DocumentSymbol, DocumentSymbolResponse};
|
||||
use nucleo::Utf32Str;
|
||||
use strum::{EnumMessage, IntoEnumIterator};
|
||||
use tracing::error;
|
||||
@@ -41,12 +45,12 @@ use crate::{
|
||||
db::LapceDb,
|
||||
debug::{RunDebugConfigs, RunDebugMode},
|
||||
editor::{
|
||||
location::{EditorLocation, EditorPosition},
|
||||
EditorData,
|
||||
location::{EditorLocation, EditorPosition},
|
||||
},
|
||||
keypress::{condition::Condition, KeyPressData, KeyPressFocus},
|
||||
keypress::{KeyPressData, KeyPressFocus, condition::Condition},
|
||||
lsp::path_from_url,
|
||||
main_split::MainSplitData,
|
||||
proxy::path_from_url,
|
||||
source_control::SourceControlData,
|
||||
window_tab::{CommonData, Focus},
|
||||
workspace::{LapceWorkspace, LapceWorkspaceType, SshHost},
|
||||
@@ -55,7 +59,7 @@ use crate::{
|
||||
pub mod item;
|
||||
pub mod kind;
|
||||
|
||||
const DEFAULT_RUN_TOML: &str = include_str!("../../defaults/run.toml");
|
||||
pub const DEFAULT_RUN_TOML: &str = include_str!("../../defaults/run.toml");
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum PaletteStatus {
|
||||
@@ -105,6 +109,12 @@ pub struct PaletteData {
|
||||
left_diff_path: RwSignal<Option<PathBuf>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PaletteData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PaletteData").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PaletteData {
|
||||
pub fn new(
|
||||
cx: Scope,
|
||||
@@ -130,7 +140,7 @@ impl PaletteData {
|
||||
let run_id = cx.create_rw_signal(0);
|
||||
let run_id_counter = Arc::new(AtomicU64::new(0));
|
||||
|
||||
let (run_tx, run_rx) = crossbeam_channel::unbounded();
|
||||
let (run_tx, run_rx) = channel();
|
||||
{
|
||||
let run_id = run_id.read_only();
|
||||
let input = input.read_only();
|
||||
@@ -145,7 +155,11 @@ impl PaletteData {
|
||||
let run_id = run_id.get_untracked();
|
||||
let preselect_index =
|
||||
preselect_index.try_update(|i| i.take()).unwrap();
|
||||
let _ = tx.send((run_id, input.input, items, preselect_index));
|
||||
if let Err(err) =
|
||||
tx.send((run_id, input.input, items, preselect_index))
|
||||
{
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
// this effect only monitors input change
|
||||
@@ -157,16 +171,21 @@ impl PaletteData {
|
||||
}
|
||||
let items = items.get_untracked();
|
||||
let run_id = run_id.get_untracked();
|
||||
let _ = tx.send((run_id, input.input, items, None));
|
||||
if let Err(err) = tx.send((run_id, input.input, items, None)) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
kind
|
||||
});
|
||||
}
|
||||
let (resp_tx, resp_rx) = crossbeam_channel::unbounded();
|
||||
let (resp_tx, resp_rx) = channel();
|
||||
{
|
||||
let run_id = run_id_counter.clone();
|
||||
std::thread::spawn(move || {
|
||||
Self::update_process(run_id, run_rx, resp_tx);
|
||||
});
|
||||
std::thread::Builder::new()
|
||||
.name("PaletteUpdateProcess".to_owned())
|
||||
.spawn(move || {
|
||||
Self::update_process(run_id, run_rx, resp_tx);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
let (filtered_items, set_filtered_items) =
|
||||
cx.create_signal(im::Vector::new());
|
||||
@@ -324,14 +343,18 @@ impl PaletteData {
|
||||
|
||||
/// Get the placeholder text to use in the palette input field.
|
||||
pub fn placeholder_text(&self) -> &'static str {
|
||||
if self.kind.get() == PaletteKind::DiffFiles {
|
||||
if self.left_diff_path.with(Option::is_some) {
|
||||
"Select right file"
|
||||
} else {
|
||||
"Seleft left file"
|
||||
match self.kind.get() {
|
||||
PaletteKind::SshHost => {
|
||||
"Type [user@]host or select a previously connected workspace below"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
PaletteKind::DiffFiles => {
|
||||
if self.left_diff_path.with(Option::is_some) {
|
||||
"Select right file"
|
||||
} else {
|
||||
"Seleft left file"
|
||||
}
|
||||
}
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +371,7 @@ impl PaletteData {
|
||||
PaletteKind::File | PaletteKind::DiffFiles => {
|
||||
self.get_files();
|
||||
}
|
||||
PaletteKind::HelpAndFile => self.get_palette_help_and_file(),
|
||||
PaletteKind::Line => {
|
||||
self.get_lines();
|
||||
}
|
||||
@@ -397,7 +421,12 @@ impl PaletteData {
|
||||
|
||||
/// Initialize the palette with a list of the available palette kinds.
|
||||
fn get_palette_help(&self) {
|
||||
let items = PaletteKind::iter()
|
||||
let items = self.get_palette_help_items();
|
||||
self.items.set(items);
|
||||
}
|
||||
|
||||
fn get_palette_help_items(&self) -> Vector<PaletteItem> {
|
||||
PaletteKind::iter()
|
||||
.filter_map(|kind| {
|
||||
// Don't include PaletteHelp as the user is already here.
|
||||
(kind != PaletteKind::PaletteHelp)
|
||||
@@ -424,13 +453,18 @@ impl PaletteData {
|
||||
indices: vec![],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.items.set(items);
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Initialize the palette with the files in the current workspace.
|
||||
fn get_files(&self) {
|
||||
fn get_palette_help_and_file(&self) {
|
||||
let help_items: Vector<PaletteItem> = self.get_palette_help_items();
|
||||
self.get_files_and_prepend(Some(help_items));
|
||||
}
|
||||
|
||||
// get the files in the current workspace
|
||||
// and prepend items if prepend is some
|
||||
// e.g. help_and_file
|
||||
fn get_files_and_prepend(&self, prepend: Option<im::Vector<PaletteItem>>) {
|
||||
let workspace = self.workspace.clone();
|
||||
let set_items = self.items.write_only();
|
||||
let send =
|
||||
@@ -457,7 +491,12 @@ impl PaletteData {
|
||||
}
|
||||
})
|
||||
.collect::<im::Vector<_>>();
|
||||
set_items.set(items);
|
||||
let mut new_items = im::Vector::new();
|
||||
if let Some(prepend) = prepend {
|
||||
new_items.append(prepend);
|
||||
}
|
||||
new_items.append(items);
|
||||
set_items.set(new_items);
|
||||
});
|
||||
self.common.proxy.get_files(move |result| {
|
||||
if let Ok(ProxyResponse::GetFilesResponse { items }) = result {
|
||||
@@ -466,6 +505,11 @@ impl PaletteData {
|
||||
});
|
||||
}
|
||||
|
||||
/// Initialize the palette with the files in the current workspace.
|
||||
fn get_files(&self) {
|
||||
self.get_files_and_prepend(None);
|
||||
}
|
||||
|
||||
/// Initialize the palette with the lines in the current document.
|
||||
fn get_lines(&self) {
|
||||
let editor = self.main_split.active_editor.get_untracked();
|
||||
@@ -636,42 +680,7 @@ impl PaletteData {
|
||||
let set_items = self.items.write_only();
|
||||
let send = create_ext_action(self.common.scope, move |result| {
|
||||
if let Ok(ProxyResponse::GetDocumentSymbols { resp }) = result {
|
||||
let items: im::Vector<PaletteItem> = match resp {
|
||||
DocumentSymbolResponse::Flat(symbols) => symbols
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let mut filter_text = s.name.clone();
|
||||
if let Some(container_name) = s.container_name.as_ref() {
|
||||
filter_text += container_name;
|
||||
}
|
||||
PaletteItem {
|
||||
content: PaletteItemContent::DocumentSymbol {
|
||||
kind: s.kind,
|
||||
name: s.name.clone(),
|
||||
range: s.location.range,
|
||||
container_name: s.container_name.clone(),
|
||||
},
|
||||
filter_text,
|
||||
score: 0,
|
||||
indices: Vec::new(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
DocumentSymbolResponse::Nested(symbols) => symbols
|
||||
.iter()
|
||||
.map(|s| PaletteItem {
|
||||
content: PaletteItemContent::DocumentSymbol {
|
||||
kind: s.kind,
|
||||
name: s.name.clone(),
|
||||
range: s.range,
|
||||
container_name: None,
|
||||
},
|
||||
filter_text: s.name.clone(),
|
||||
score: 0,
|
||||
indices: Vec::new(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let items = Self::format_document_symbol_resp(resp);
|
||||
set_items.set(items);
|
||||
} else {
|
||||
set_items.update(|items| items.clear());
|
||||
@@ -683,6 +692,64 @@ impl PaletteData {
|
||||
});
|
||||
}
|
||||
|
||||
fn format_document_symbol_resp(
|
||||
resp: DocumentSymbolResponse,
|
||||
) -> im::Vector<PaletteItem> {
|
||||
match resp {
|
||||
DocumentSymbolResponse::Flat(symbols) => symbols
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let mut filter_text = s.name.clone();
|
||||
if let Some(container_name) = s.container_name.as_ref() {
|
||||
filter_text += container_name;
|
||||
}
|
||||
PaletteItem {
|
||||
content: PaletteItemContent::DocumentSymbol {
|
||||
kind: s.kind,
|
||||
name: s.name.replace('\n', "↵"),
|
||||
range: s.location.range,
|
||||
container_name: s.container_name.clone(),
|
||||
},
|
||||
filter_text,
|
||||
score: 0,
|
||||
indices: Vec::new(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
DocumentSymbolResponse::Nested(symbols) => {
|
||||
let mut items = im::Vector::new();
|
||||
for s in symbols {
|
||||
Self::format_document_symbol(&mut items, None, s)
|
||||
}
|
||||
items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_document_symbol(
|
||||
items: &mut im::Vector<PaletteItem>,
|
||||
parent: Option<String>,
|
||||
s: DocumentSymbol,
|
||||
) {
|
||||
items.push_back(PaletteItem {
|
||||
content: PaletteItemContent::DocumentSymbol {
|
||||
kind: s.kind,
|
||||
name: s.name.replace('\n', "↵"),
|
||||
range: s.range,
|
||||
container_name: parent,
|
||||
},
|
||||
filter_text: s.name.clone(),
|
||||
score: 0,
|
||||
indices: Vec::new(),
|
||||
});
|
||||
if let Some(children) = s.children {
|
||||
let parent = Some(s.name.replace('\n', "↵"));
|
||||
for child in children {
|
||||
Self::format_document_symbol(items, parent.clone(), child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_workspace_symbols(&self) {
|
||||
let input = self.input.get_untracked().input;
|
||||
|
||||
@@ -755,8 +822,7 @@ impl PaletteData {
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_wsl_hosts(&self) {
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::process;
|
||||
use std::{os::windows::process::CommandExt, process};
|
||||
let cmd = process::Command::new("wsl")
|
||||
.creation_flags(0x08000000) // CREATE_NO_WINDOW
|
||||
.arg("-l")
|
||||
@@ -877,7 +943,7 @@ impl PaletteData {
|
||||
fn get_run_configs(&self) {
|
||||
if let Some(workspace) = self.common.workspace.path.as_deref() {
|
||||
let run_toml = workspace.join(".lapce").join("run.toml");
|
||||
let (doc, new_doc) = self.main_split.get_doc(run_toml.clone());
|
||||
let (doc, new_doc) = self.main_split.get_doc(run_toml.clone(), None);
|
||||
if !new_doc {
|
||||
let content = doc.buffer.with_untracked(|b| b.to_string());
|
||||
self.set_run_configs(content);
|
||||
@@ -1331,7 +1397,7 @@ impl PaletteData {
|
||||
PaletteItemContent::Reference { location, .. } => {
|
||||
self.has_preview.set(true);
|
||||
let (doc, new_doc) =
|
||||
self.main_split.get_doc(location.path.clone());
|
||||
self.main_split.get_doc(location.path.clone(), None);
|
||||
self.preview_editor.update_doc(doc);
|
||||
self.preview_editor.go_to_location(
|
||||
location.clone(),
|
||||
@@ -1371,7 +1437,7 @@ impl PaletteData {
|
||||
PaletteItemContent::WorkspaceSymbol { location, .. } => {
|
||||
self.has_preview.set(true);
|
||||
let (doc, new_doc) =
|
||||
self.main_split.get_doc(location.path.clone());
|
||||
self.main_split.get_doc(location.path.clone(), None);
|
||||
self.preview_editor.update_doc(doc);
|
||||
self.preview_editor.go_to_location(
|
||||
location.clone(),
|
||||
@@ -1497,6 +1563,8 @@ impl PaletteData {
|
||||
// NOTE: We collect into a Vec to sort as we are hitting a worst-case behavior in
|
||||
// `im::Vector` that can lead to a stack overflow!
|
||||
let mut filtered_items = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
let mut filter_text_buf = Vec::new();
|
||||
for i in &items {
|
||||
// If the run id has ever changed, then we'll just bail out of this filtering to avoid
|
||||
// wasting effort. This would happen, for example, on the user continuing to type.
|
||||
@@ -1504,14 +1572,14 @@ impl PaletteData {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut indices = Vec::new();
|
||||
let mut filter_text_buf = Vec::new();
|
||||
indices.clear();
|
||||
filter_text_buf.clear();
|
||||
let filter_text = Utf32Str::new(&i.filter_text, &mut filter_text_buf);
|
||||
if let Some(score) = pattern.indices(filter_text, matcher, &mut indices)
|
||||
{
|
||||
let mut item = i.clone();
|
||||
item.score = score;
|
||||
item.indices = indices.into_iter().map(|i| i as usize).collect();
|
||||
item.indices = indices.iter().map(|i| *i as usize).collect();
|
||||
filtered_items.push(item);
|
||||
}
|
||||
}
|
||||
@@ -1573,12 +1641,14 @@ impl PaletteData {
|
||||
items,
|
||||
&mut matcher,
|
||||
) {
|
||||
let _ = resp_tx.send((
|
||||
if let Err(err) = resp_tx.send((
|
||||
current_run_id,
|
||||
input,
|
||||
filtered_items,
|
||||
preselect_index,
|
||||
));
|
||||
)) {
|
||||
tracing::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum PaletteKind {
|
||||
SCMReferences,
|
||||
TerminalProfile,
|
||||
DiffFiles,
|
||||
HelpAndFile,
|
||||
}
|
||||
|
||||
impl PaletteKind {
|
||||
@@ -46,6 +47,7 @@ impl PaletteKind {
|
||||
| PaletteKind::Language
|
||||
| PaletteKind::LineEnding
|
||||
| PaletteKind::SCMReferences
|
||||
| PaletteKind::HelpAndFile
|
||||
| PaletteKind::DiffFiles => "",
|
||||
#[cfg(windows)]
|
||||
PaletteKind::WslHost => "",
|
||||
@@ -80,6 +82,9 @@ impl PaletteKind {
|
||||
PaletteKind::Workspace => Some(LapceWorkbenchCommand::PaletteWorkspace),
|
||||
PaletteKind::Command => Some(LapceWorkbenchCommand::PaletteCommand),
|
||||
PaletteKind::File => Some(LapceWorkbenchCommand::Palette),
|
||||
PaletteKind::HelpAndFile => {
|
||||
Some(LapceWorkbenchCommand::PaletteHelpAndFile)
|
||||
}
|
||||
PaletteKind::Reference => None, // InternalCommand::PaletteReferences
|
||||
PaletteKind::SshHost => Some(LapceWorkbenchCommand::ConnectSshHost),
|
||||
#[cfg(windows)]
|
||||
@@ -124,7 +129,7 @@ impl PaletteKind {
|
||||
| PaletteKind::IconTheme
|
||||
| PaletteKind::Language
|
||||
| PaletteKind::LineEnding
|
||||
| PaletteKind::SCMReferences
|
||||
| PaletteKind::SCMReferences | PaletteKind::HelpAndFile
|
||||
| PaletteKind::DiffFiles => input,
|
||||
PaletteKind::PaletteHelp
|
||||
| PaletteKind::Command
|
||||
@@ -141,9 +146,17 @@ impl PaletteKind {
|
||||
/// Get the palette kind that it should be considered as based on the current
|
||||
/// [`PaletteKind`] and the current input.
|
||||
pub fn get_palette_kind(&self, input: &str) -> PaletteKind {
|
||||
if self != &PaletteKind::File && self.symbol() == "" {
|
||||
if self == &PaletteKind::HelpAndFile && input.is_empty() {
|
||||
return *self;
|
||||
}
|
||||
|
||||
if self != &PaletteKind::File
|
||||
&& self != &PaletteKind::HelpAndFile
|
||||
&& self.symbol() == ""
|
||||
{
|
||||
return *self;
|
||||
}
|
||||
|
||||
PaletteKind::from_input(input)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
use std::{ops::AddAssign, rc::Rc};
|
||||
|
||||
use floem::{
|
||||
IntoView, View, ViewId,
|
||||
reactive::{RwSignal, SignalGet, SignalUpdate, SignalWith},
|
||||
style::CursorStyle,
|
||||
views::{
|
||||
Decorators, VirtualVector, container, empty, label, scroll, stack, svg,
|
||||
virtual_stack,
|
||||
},
|
||||
};
|
||||
use lsp_types::{CallHierarchyItem, Range};
|
||||
|
||||
use super::position::PanelPosition;
|
||||
use crate::{
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons},
|
||||
editor::location::EditorLocation,
|
||||
window_tab::{CommonData, WindowTabData},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CallHierarchyData {
|
||||
pub root: RwSignal<Option<RwSignal<CallHierarchyItemData>>>,
|
||||
pub common: Rc<CommonData>,
|
||||
pub scroll_to_line: RwSignal<Option<f64>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CallHierarchyItemData {
|
||||
pub view_id: ViewId,
|
||||
pub item: Rc<CallHierarchyItem>,
|
||||
pub from_range: Range,
|
||||
pub init: bool,
|
||||
pub open: RwSignal<bool>,
|
||||
pub children: RwSignal<Vec<RwSignal<CallHierarchyItemData>>>,
|
||||
}
|
||||
|
||||
impl CallHierarchyItemData {
|
||||
pub fn child_count(&self) -> usize {
|
||||
let mut count = 1;
|
||||
if self.open.get() {
|
||||
for child in self.children.get_untracked() {
|
||||
count += child.with(|x| x.child_count())
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub fn find_by_id(
|
||||
root: RwSignal<CallHierarchyItemData>,
|
||||
view_id: ViewId,
|
||||
) -> Option<RwSignal<CallHierarchyItemData>> {
|
||||
if root.get_untracked().view_id == view_id {
|
||||
Some(root)
|
||||
} else {
|
||||
root.get_untracked()
|
||||
.children
|
||||
.get_untracked()
|
||||
.into_iter()
|
||||
.find_map(|x| Self::find_by_id(x, view_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_children(
|
||||
data: RwSignal<CallHierarchyItemData>,
|
||||
next: &mut usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
level: usize,
|
||||
) -> Vec<(usize, usize, RwSignal<CallHierarchyItemData>)> {
|
||||
let mut children = Vec::new();
|
||||
if *next >= min && *next < max {
|
||||
children.push((*next, level, data));
|
||||
} else if *next >= max {
|
||||
return children;
|
||||
}
|
||||
next.add_assign(1);
|
||||
if data.get_untracked().open.get() {
|
||||
for child in data.get().children.get_untracked() {
|
||||
let child_children = get_children(child, next, min, max, level + 1);
|
||||
children.extend(child_children);
|
||||
if *next > max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
|
||||
pub struct VirtualList {
|
||||
root: Option<RwSignal<CallHierarchyItemData>>,
|
||||
}
|
||||
|
||||
impl VirtualList {
|
||||
pub fn new(root: Option<RwSignal<CallHierarchyItemData>>) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualVector<(usize, usize, RwSignal<CallHierarchyItemData>)> for VirtualList {
|
||||
fn total_len(&self) -> usize {
|
||||
if let Some(root) = &self.root {
|
||||
root.with(|x| x.child_count())
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn slice(
|
||||
&mut self,
|
||||
range: std::ops::Range<usize>,
|
||||
) -> impl Iterator<Item = (usize, usize, RwSignal<CallHierarchyItemData>)> {
|
||||
if let Some(root) = &self.root {
|
||||
let min = range.start;
|
||||
let max = range.end;
|
||||
let children = get_children(*root, &mut 0, min, max, 0);
|
||||
children.into_iter()
|
||||
} else {
|
||||
Vec::new().into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn show_hierarchy_panel(
|
||||
window_tab_data: Rc<WindowTabData>,
|
||||
_position: PanelPosition,
|
||||
) -> impl View {
|
||||
let call_hierarchy_data = window_tab_data.call_hierarchy_data.clone();
|
||||
let config = call_hierarchy_data.common.config;
|
||||
let ui_line_height = call_hierarchy_data.common.ui_line_height;
|
||||
let scroll_to_line = call_hierarchy_data.scroll_to_line;
|
||||
scroll(
|
||||
virtual_stack(
|
||||
move || VirtualList::new(call_hierarchy_data.root.get()),
|
||||
move |(_, _, item)| item.get_untracked().view_id,
|
||||
move |(_, level, rw_data)| {
|
||||
let data = rw_data.get_untracked();
|
||||
let open = data.open;
|
||||
let kind = data.item.kind;
|
||||
stack((
|
||||
container(
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
let svg_str = match open.get() {
|
||||
true => LapceIcons::ITEM_OPENED,
|
||||
false => LapceIcons::ITEM_CLOSED,
|
||||
};
|
||||
config.ui_svg(svg_str)
|
||||
})
|
||||
.style(move |s| {
|
||||
let config = config.get();
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.size(size, size)
|
||||
.color(config.color(LapceColor::LAPCE_ICON_ACTIVE))
|
||||
})
|
||||
)
|
||||
.style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0))
|
||||
.on_click_stop({
|
||||
let window_tab_data = window_tab_data.clone();
|
||||
move |_x| {
|
||||
open.update(|x| {
|
||||
*x = !*x;
|
||||
});
|
||||
if !rw_data.get_untracked().init {
|
||||
window_tab_data.common.internal_command.send(
|
||||
InternalCommand::CallHierarchyIncoming {
|
||||
item_id: rw_data.get_untracked().view_id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
config
|
||||
.symbol_svg(&kind)
|
||||
.unwrap_or_else(|| config.ui_svg(LapceIcons::FILE))
|
||||
}).style(move |s| {
|
||||
let config = config.get();
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.min_width(size)
|
||||
.size(size, size)
|
||||
.margin_right(5.0)
|
||||
.color(config.symbol_color(&kind).unwrap_or_else(|| {
|
||||
config.color(LapceColor::LAPCE_ICON_ACTIVE)
|
||||
}))
|
||||
}),
|
||||
data.item.name.clone().into_view(),
|
||||
if data.item.detail.is_some() {
|
||||
label(move || {
|
||||
data.item.detail.clone().unwrap_or_default().replace('\n', "↵")
|
||||
}).style(move |s| s.margin_left(6.0)
|
||||
.color(config.get().color(LapceColor::EDITOR_DIM))
|
||||
).into_any()
|
||||
} else {
|
||||
empty().into_any()
|
||||
},
|
||||
))
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.height(ui_line_height.get())
|
||||
.padding_left((level * 10) as f32)
|
||||
.items_center()
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
})
|
||||
.on_click_stop({
|
||||
let window_tab_data = window_tab_data.clone();
|
||||
let data = rw_data;
|
||||
move |_| {
|
||||
if !rw_data.get_untracked().init {
|
||||
window_tab_data.common.internal_command.send(
|
||||
InternalCommand::CallHierarchyIncoming { item_id: rw_data.get_untracked().view_id },
|
||||
);
|
||||
}
|
||||
let data = data.get_untracked();
|
||||
if let Ok(path) = data.item.uri.to_file_path() {
|
||||
window_tab_data
|
||||
.common
|
||||
.internal_command
|
||||
.send(InternalCommand::JumpToLocation { location: EditorLocation {
|
||||
path,
|
||||
position: Some(crate::editor::location::EditorPosition::Position(data.from_range.start)),
|
||||
scroll_offset: None,
|
||||
ignore_unconfirmed: false,
|
||||
same_editor_tab: false,
|
||||
} });
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
).item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.flex_col().absolute().min_width_full()),
|
||||
)
|
||||
.style(|s| s.absolute().size_full())
|
||||
.scroll_to(move || {
|
||||
if let Some(line) = scroll_to_line.get() {
|
||||
let line_height = ui_line_height.get();
|
||||
Some((0.0, line * line_height).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,9 @@ use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
kurbo::Size,
|
||||
reactive::{use_context, Memo, RwSignal, Scope},
|
||||
reactive::{
|
||||
Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, use_context,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -31,12 +33,38 @@ pub fn default_panel_order() -> PanelOrder {
|
||||
);
|
||||
order.insert(
|
||||
PanelPosition::BottomLeft,
|
||||
im::vector![PanelKind::Terminal, PanelKind::Search, PanelKind::Problem,],
|
||||
im::vector![
|
||||
PanelKind::Terminal,
|
||||
PanelKind::Search,
|
||||
PanelKind::Problem,
|
||||
PanelKind::CallHierarchy,
|
||||
PanelKind::References,
|
||||
PanelKind::Implementation
|
||||
],
|
||||
);
|
||||
order.insert(
|
||||
PanelPosition::RightTop,
|
||||
im::vector![PanelKind::DocumentSymbol,],
|
||||
);
|
||||
|
||||
order
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub enum PanelSection {
|
||||
OpenEditor,
|
||||
FileExplorer,
|
||||
Error,
|
||||
Warn,
|
||||
Changes,
|
||||
Installed,
|
||||
Available,
|
||||
Process,
|
||||
Variable,
|
||||
StackFrame,
|
||||
Breakpoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct PanelSize {
|
||||
pub left: f64,
|
||||
@@ -52,6 +80,7 @@ pub struct PanelInfo {
|
||||
pub panels: PanelOrder,
|
||||
pub styles: im::HashMap<PanelPosition, PanelStyle>,
|
||||
pub size: PanelSize,
|
||||
pub sections: im::HashMap<PanelSection, bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -60,6 +89,7 @@ pub struct PanelData {
|
||||
pub styles: RwSignal<im::HashMap<PanelPosition, PanelStyle>>,
|
||||
pub size: RwSignal<PanelSize>,
|
||||
pub available_size: Memo<Size>,
|
||||
pub sections: RwSignal<im::HashMap<PanelSection, RwSignal<bool>>>,
|
||||
pub common: Rc<CommonData>,
|
||||
}
|
||||
|
||||
@@ -68,6 +98,7 @@ impl PanelData {
|
||||
cx: Scope,
|
||||
panels: im::HashMap<PanelPosition, im::Vector<PanelKind>>,
|
||||
available_size: Memo<Size>,
|
||||
sections: im::HashMap<PanelSection, bool>,
|
||||
common: Rc<CommonData>,
|
||||
) -> Self {
|
||||
let panels = cx.create_rw_signal(panels);
|
||||
@@ -130,12 +161,19 @@ impl PanelData {
|
||||
right: 250.0,
|
||||
right_split: 0.5,
|
||||
});
|
||||
let sections = cx.create_rw_signal(
|
||||
sections
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key, cx.create_rw_signal(value)))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
Self {
|
||||
panels,
|
||||
styles,
|
||||
size,
|
||||
available_size,
|
||||
sections,
|
||||
common,
|
||||
}
|
||||
}
|
||||
@@ -145,6 +183,12 @@ impl PanelData {
|
||||
panels: self.panels.get_untracked(),
|
||||
styles: self.styles.get_untracked(),
|
||||
size: self.size.get_untracked(),
|
||||
sections: self
|
||||
.sections
|
||||
.get_untracked()
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key, value.get_untracked()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +433,21 @@ impl PanelData {
|
||||
let db: Arc<LapceDb> = use_context().unwrap();
|
||||
db.save_panel_orders(self.panels.get_untracked());
|
||||
}
|
||||
|
||||
pub fn section_open(&self, section: PanelSection) -> RwSignal<bool> {
|
||||
let open = self
|
||||
.sections
|
||||
.with_untracked(|sections| sections.get(§ion).cloned());
|
||||
if let Some(open) = open {
|
||||
return open;
|
||||
}
|
||||
|
||||
let open = self.common.scope.create_rw_signal(true);
|
||||
self.sections.update(|sections| {
|
||||
sections.insert(section, open);
|
||||
});
|
||||
open
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panel_position(
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
cosmic_text::Style as FontStyle,
|
||||
View,
|
||||
event::EventListener,
|
||||
peniko::Color,
|
||||
reactive::{create_rw_signal, ReadSignal, RwSignal},
|
||||
reactive::{
|
||||
ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, create_rw_signal,
|
||||
},
|
||||
style::CursorStyle,
|
||||
view::View,
|
||||
text::Style as FontStyle,
|
||||
views::{
|
||||
container, dyn_stack, label, scroll, stack, svg, text, virtual_stack,
|
||||
Decorators, VirtualDirection, VirtualItemSize,
|
||||
Decorators, container, dyn_stack, label, scroll, stack, svg, text,
|
||||
virtual_stack,
|
||||
},
|
||||
};
|
||||
use lapce_rpc::{
|
||||
@@ -17,11 +19,11 @@ use lapce_rpc::{
|
||||
terminal::TermId,
|
||||
};
|
||||
|
||||
use super::{position::PanelPosition, view::PanelBuilder};
|
||||
use super::{data::PanelSection, position::PanelPosition, view::PanelBuilder};
|
||||
use crate::{
|
||||
app::clickable_icon,
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor, icon::LapceIcons},
|
||||
debug::{DapVariable, RunDebugMode, StackTraceData},
|
||||
editor::location::{EditorLocation, EditorPosition},
|
||||
listener::Listener,
|
||||
@@ -43,18 +45,26 @@ pub fn debug_panel(
|
||||
"Processes",
|
||||
150.0,
|
||||
debug_processes(terminal.clone(), config),
|
||||
window_tab_data.panel.section_open(PanelSection::Process),
|
||||
)
|
||||
.add(
|
||||
"Variables",
|
||||
variables_view(window_tab_data.clone()),
|
||||
window_tab_data.panel.section_open(PanelSection::Variable),
|
||||
)
|
||||
.add("Variables", variables_view(window_tab_data.clone()))
|
||||
.add(
|
||||
"Stack Frames",
|
||||
debug_stack_traces(terminal.clone(), internal_command, config),
|
||||
window_tab_data.panel.section_open(PanelSection::StackFrame),
|
||||
)
|
||||
.add_height(
|
||||
"Breakpoints",
|
||||
150.0,
|
||||
breakpoints_view(window_tab_data.clone()),
|
||||
window_tab_data.panel.section_open(PanelSection::Breakpoint),
|
||||
)
|
||||
.build()
|
||||
.debug_name("Debug Panel")
|
||||
}
|
||||
|
||||
fn debug_process_icons(
|
||||
@@ -310,7 +320,7 @@ fn debug_processes(
|
||||
.hover(|s| {
|
||||
s.cursor(CursorStyle::Pointer).background(
|
||||
(config.color(LapceColor::PANEL_HOVERED_BACKGROUND))
|
||||
.with_alpha_factor(0.3),
|
||||
.multiply_alpha(0.3),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -328,8 +338,6 @@ fn variables_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
container(
|
||||
scroll(
|
||||
virtual_stack(
|
||||
VirtualDirection::Vertical,
|
||||
VirtualItemSize::Fixed(Box::new(move || ui_line_height.get())),
|
||||
move || {
|
||||
let dap = terminal.get_active_dap(true);
|
||||
dap.map(|dap| {
|
||||
@@ -435,6 +443,7 @@ fn variables_view(window_tab_data: Rc<WindowTabData>) -> impl View {
|
||||
})
|
||||
},
|
||||
)
|
||||
.item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.flex_col().min_width_full()),
|
||||
)
|
||||
.style(|s| s.absolute().size_full()),
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
use std::{ops::AddAssign, path::PathBuf, rc::Rc};
|
||||
|
||||
use floem::{
|
||||
View,
|
||||
peniko::Color,
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith},
|
||||
style::CursorStyle,
|
||||
views::{
|
||||
Decorators, VirtualVector, container, editor::id::Id, label, scroll, stack,
|
||||
svg, virtual_stack,
|
||||
},
|
||||
};
|
||||
use lsp_types::{DocumentSymbol, SymbolKind};
|
||||
|
||||
use super::position::PanelPosition;
|
||||
use crate::{
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons},
|
||||
editor::location::EditorLocation,
|
||||
window_tab::WindowTabData,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SymbolData {
|
||||
pub path: PathBuf,
|
||||
pub file: RwSignal<SymbolInformationItemData>,
|
||||
}
|
||||
|
||||
impl SymbolData {
|
||||
pub fn new(
|
||||
items: Vec<RwSignal<SymbolInformationItemData>>,
|
||||
path: PathBuf,
|
||||
cx: Scope,
|
||||
) -> Self {
|
||||
let name = path
|
||||
.file_name()
|
||||
.and_then(|x| x.to_str())
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default();
|
||||
#[allow(deprecated)]
|
||||
let file_ds = DocumentSymbol {
|
||||
name: name.clone(),
|
||||
detail: None,
|
||||
kind: SymbolKind::FILE,
|
||||
tags: None,
|
||||
deprecated: None,
|
||||
range: Default::default(),
|
||||
selection_range: Default::default(),
|
||||
children: None,
|
||||
};
|
||||
let file = cx.create_rw_signal(SymbolInformationItemData {
|
||||
id: Id::next(),
|
||||
name,
|
||||
detail: None,
|
||||
item: file_ds,
|
||||
open: cx.create_rw_signal(true),
|
||||
children: items,
|
||||
});
|
||||
Self { path, file }
|
||||
}
|
||||
fn get_children(
|
||||
&self,
|
||||
min: usize,
|
||||
max: usize,
|
||||
) -> Vec<(
|
||||
usize,
|
||||
usize,
|
||||
Rc<PathBuf>,
|
||||
RwSignal<SymbolInformationItemData>,
|
||||
)> {
|
||||
let path = Rc::new(self.path.clone());
|
||||
let level: usize = 0;
|
||||
let mut next = 0;
|
||||
get_children(self.file, &mut next, min, max, level, path.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SymbolInformationItemData {
|
||||
pub id: Id,
|
||||
pub name: String,
|
||||
pub detail: Option<String>,
|
||||
pub item: DocumentSymbol,
|
||||
pub open: RwSignal<bool>,
|
||||
pub children: Vec<RwSignal<SymbolInformationItemData>>,
|
||||
}
|
||||
|
||||
impl From<(DocumentSymbol, Scope)> for SymbolInformationItemData {
|
||||
fn from((mut item, cx): (DocumentSymbol, Scope)) -> Self {
|
||||
let children = if let Some(children) = item.children.take() {
|
||||
children
|
||||
.into_iter()
|
||||
.map(|x| cx.create_rw_signal(Self::from((x, cx))))
|
||||
.collect()
|
||||
} else {
|
||||
Vec::with_capacity(0)
|
||||
};
|
||||
Self {
|
||||
id: Id::next(),
|
||||
name: item.name.clone(),
|
||||
detail: item.detail.clone(),
|
||||
item,
|
||||
open: cx.create_rw_signal(true),
|
||||
children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolInformationItemData {
|
||||
pub fn child_count(&self) -> usize {
|
||||
let mut count = 1;
|
||||
if self.open.get() {
|
||||
for child in &self.children {
|
||||
count += child.with(|x| x.child_count())
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
fn get_children(
|
||||
data: RwSignal<SymbolInformationItemData>,
|
||||
next: &mut usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
level: usize,
|
||||
path: Rc<PathBuf>,
|
||||
) -> Vec<(
|
||||
usize,
|
||||
usize,
|
||||
Rc<PathBuf>,
|
||||
RwSignal<SymbolInformationItemData>,
|
||||
)> {
|
||||
let mut children = Vec::new();
|
||||
if *next >= min && *next < max {
|
||||
children.push((*next, level, path.clone(), data));
|
||||
} else if *next >= max {
|
||||
return children;
|
||||
}
|
||||
next.add_assign(1);
|
||||
if data.get_untracked().open.get() {
|
||||
for child in data.get().children {
|
||||
let child_children =
|
||||
get_children(child, next, min, max, level + 1, path.clone());
|
||||
children.extend(child_children);
|
||||
if *next > max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
|
||||
pub struct VirtualList {
|
||||
root: Option<RwSignal<Option<SymbolData>>>,
|
||||
}
|
||||
|
||||
impl VirtualList {
|
||||
pub fn new(root: Option<RwSignal<Option<SymbolData>>>) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
VirtualVector<(
|
||||
usize,
|
||||
usize,
|
||||
Rc<PathBuf>,
|
||||
RwSignal<SymbolInformationItemData>,
|
||||
)> for VirtualList
|
||||
{
|
||||
fn total_len(&self) -> usize {
|
||||
if let Some(root) = self.root.as_ref().and_then(|x| x.get()) {
|
||||
root.file.get_untracked().child_count()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn slice(
|
||||
&mut self,
|
||||
range: std::ops::Range<usize>,
|
||||
) -> impl Iterator<
|
||||
Item = (
|
||||
usize,
|
||||
usize,
|
||||
Rc<PathBuf>,
|
||||
RwSignal<SymbolInformationItemData>,
|
||||
),
|
||||
> {
|
||||
if let Some(root) = self.root.as_ref().and_then(|x| x.get()) {
|
||||
let min = range.start;
|
||||
let max = range.end;
|
||||
let children = root.get_children(min, max);
|
||||
children.into_iter()
|
||||
} else {
|
||||
Vec::new().into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbol_panel(
|
||||
window_tab_data: Rc<WindowTabData>,
|
||||
_position: PanelPosition,
|
||||
) -> impl View {
|
||||
let config = window_tab_data.common.config;
|
||||
let ui_line_height = window_tab_data.common.ui_line_height;
|
||||
scroll(
|
||||
virtual_stack(
|
||||
{
|
||||
let window_tab_data = window_tab_data.clone();
|
||||
move || {
|
||||
let editor = window_tab_data.main_split.get_active_editor();
|
||||
VirtualList::new(editor.map(|x| x.doc().document_symbol_data))
|
||||
}
|
||||
},
|
||||
move |(_, _, _, item)| item.get_untracked().id,
|
||||
move |(_, level, path, rw_data)| {
|
||||
let data = rw_data.get_untracked();
|
||||
let open = data.open;
|
||||
let has_child = !data.children.is_empty();
|
||||
let kind = data.item.kind;
|
||||
stack((
|
||||
container(
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
let svg_str = match open.get() {
|
||||
true => LapceIcons::ITEM_OPENED,
|
||||
false => LapceIcons::ITEM_CLOSED,
|
||||
};
|
||||
config.ui_svg(svg_str)
|
||||
})
|
||||
.style(move |s| {
|
||||
let config = config.get();
|
||||
let color = if has_child {
|
||||
config.color(LapceColor::LAPCE_ICON_ACTIVE)
|
||||
} else {
|
||||
Color::TRANSPARENT
|
||||
};
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.size(size, size)
|
||||
.color(color)
|
||||
})
|
||||
).style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0))
|
||||
.on_click_stop({
|
||||
move |_x| {
|
||||
if has_child {
|
||||
open.update(|x| {
|
||||
*x = !*x;
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
config
|
||||
.symbol_svg(&kind)
|
||||
.unwrap_or_else(|| config.ui_svg(LapceIcons::FILE))
|
||||
}).style(move |s| {
|
||||
let config = config.get();
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.min_width(size)
|
||||
.size(size, size)
|
||||
.margin_right(5.0)
|
||||
.color(config.symbol_color(&kind).unwrap_or_else(|| {
|
||||
config.color(LapceColor::LAPCE_ICON_ACTIVE)
|
||||
}))
|
||||
}),
|
||||
label(move || {
|
||||
data.name.replace('\n', "↵")
|
||||
})
|
||||
.style(move |s| {
|
||||
s.selectable(false)
|
||||
}),
|
||||
label(move || {
|
||||
data.detail.clone().unwrap_or_default()
|
||||
}).style(move |s| s.margin_left(6.0)
|
||||
.color(config.get().color(LapceColor::EDITOR_DIM))
|
||||
.selectable(false)
|
||||
.apply_if(
|
||||
data.item.detail.clone().is_none(),
|
||||
|s| s.hide())
|
||||
),
|
||||
))
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.padding_left((level * 10) as f32)
|
||||
.items_center()
|
||||
.height(ui_line_height.get())
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
})
|
||||
.on_click_stop({
|
||||
let window_tab_data = window_tab_data.clone();
|
||||
let data = rw_data;
|
||||
move |_| {
|
||||
let data = data.get_untracked();
|
||||
window_tab_data
|
||||
.common
|
||||
.internal_command
|
||||
.send(InternalCommand::JumpToLocation { location: EditorLocation {
|
||||
path: path.to_path_buf(),
|
||||
position: Some(crate::editor::location::EditorPosition::Position(data.item.selection_range.start)),
|
||||
scroll_offset: None,
|
||||
ignore_unconfirmed: false,
|
||||
same_editor_tab: false,
|
||||
} });
|
||||
}
|
||||
})
|
||||
},
|
||||
).item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.flex_col().absolute().min_width_full()),
|
||||
)
|
||||
.style(|s| s.absolute().size_full())
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
use std::{path::PathBuf, rc::Rc, sync::Arc};
|
||||
|
||||
use floem::{
|
||||
View,
|
||||
event::EventListener,
|
||||
reactive::ReadSignal,
|
||||
reactive::{ReadSignal, SignalGet, SignalUpdate},
|
||||
style::{CursorStyle, Style},
|
||||
view::View,
|
||||
views::{
|
||||
container, label, scroll, stack, svg, virtual_stack, Decorators,
|
||||
VirtualDirection, VirtualItemSize,
|
||||
},
|
||||
views::{Decorators, container, label, scroll, stack, svg, virtual_stack},
|
||||
};
|
||||
use lapce_xi_rope::find::CaseMatching;
|
||||
|
||||
@@ -16,7 +13,7 @@ use super::{kind::PanelKind, position::PanelPosition};
|
||||
use crate::{
|
||||
app::clickable_icon,
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
|
||||
config::{LapceConfig, color::LapceColor, icon::LapceIcons},
|
||||
editor::location::{EditorLocation, EditorPosition},
|
||||
focus_text::focus_text,
|
||||
global_search::{GlobalSearchData, SearchMatchData},
|
||||
@@ -107,6 +104,7 @@ pub fn global_search_panel(
|
||||
search_result(workspace, global_search, internal_command, config),
|
||||
))
|
||||
.style(|s| s.absolute().size_pct(100.0, 100.0).flex_col())
|
||||
.debug_name("Global Search Panel")
|
||||
}
|
||||
|
||||
fn search_result(
|
||||
@@ -119,12 +117,6 @@ fn search_result(
|
||||
container({
|
||||
scroll({
|
||||
virtual_stack(
|
||||
VirtualDirection::Vertical,
|
||||
VirtualItemSize::Fn(Box::new(
|
||||
|(_, match_data): &(PathBuf, SearchMatchData)| {
|
||||
match_data.height()
|
||||
},
|
||||
)),
|
||||
move || global_search_data.clone(),
|
||||
move |(path, _)| path.to_owned(),
|
||||
move |(path, match_data)| {
|
||||
@@ -216,10 +208,6 @@ fn search_result(
|
||||
})
|
||||
}),
|
||||
virtual_stack(
|
||||
VirtualDirection::Vertical,
|
||||
VirtualItemSize::Fixed(Box::new(move || {
|
||||
ui_line_height.get()
|
||||
})),
|
||||
move || {
|
||||
if expanded.get() {
|
||||
match_data.matches.get()
|
||||
@@ -304,12 +292,16 @@ fn search_result(
|
||||
)
|
||||
},
|
||||
)
|
||||
.item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.flex_col()),
|
||||
))
|
||||
.style(|s| s.flex_col())
|
||||
},
|
||||
)
|
||||
.style(|s| s.flex_col().min_width_pct(100.0).line_height(1.6))
|
||||
.item_size_fn(|(_, match_data): &(PathBuf, SearchMatchData)| {
|
||||
match_data.height()
|
||||
})
|
||||
.style(|s| s.flex_col().min_width_pct(100.0).line_height(1.8))
|
||||
})
|
||||
.style(|s| s.absolute().size_pct(100.0, 100.0))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
use std::{ops::AddAssign, path::PathBuf, rc::Rc};
|
||||
|
||||
use floem::{
|
||||
IntoView, View, ViewId,
|
||||
reactive::{RwSignal, Scope, SignalGet, SignalUpdate},
|
||||
style::CursorStyle,
|
||||
views::{
|
||||
Decorators, VirtualVector, container, label, scroll, stack, svg,
|
||||
virtual_stack,
|
||||
},
|
||||
};
|
||||
use im::HashMap;
|
||||
use itertools::Itertools;
|
||||
use lapce_rpc::file_line::FileLine;
|
||||
use lsp_types::{Location, SymbolKind, request::GotoImplementationResponse};
|
||||
|
||||
use super::position::PanelPosition;
|
||||
use crate::{
|
||||
command::InternalCommand,
|
||||
config::{color::LapceColor, icon::LapceIcons},
|
||||
editor::location::EditorLocation,
|
||||
window_tab::WindowTabData,
|
||||
};
|
||||
|
||||
pub fn implementation_panel(
|
||||
window_tab_data: Rc<WindowTabData>,
|
||||
_position: PanelPosition,
|
||||
) -> impl View {
|
||||
common_reference_panel(window_tab_data.clone(), _position, move || {
|
||||
window_tab_data.main_split.implementations.get()
|
||||
})
|
||||
.debug_name("implementation panel")
|
||||
}
|
||||
pub fn common_reference_panel(
|
||||
window_tab_data: Rc<WindowTabData>,
|
||||
_position: PanelPosition,
|
||||
each_fn: impl Fn() -> ReferencesRoot + 'static,
|
||||
) -> impl View {
|
||||
let config = window_tab_data.common.config;
|
||||
let ui_line_height = window_tab_data.common.ui_line_height;
|
||||
scroll(
|
||||
virtual_stack(
|
||||
each_fn,
|
||||
move |(_, _, data)| data.view_id(),
|
||||
move |(_, level, rw_data)| {
|
||||
match rw_data {
|
||||
ReferenceLocation::File { path, open, .. } => stack((
|
||||
container(
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
let svg_str = match open.get() {
|
||||
true => LapceIcons::ITEM_OPENED,
|
||||
false => LapceIcons::ITEM_CLOSED,
|
||||
};
|
||||
config.ui_svg(svg_str)
|
||||
})
|
||||
.style(move |s| {
|
||||
let config = config.get();
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.size(size, size).color(
|
||||
config.color(LapceColor::LAPCE_ICON_ACTIVE),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0))
|
||||
.on_click_stop({
|
||||
move |_x| {
|
||||
open.update(|x| {
|
||||
*x = !*x;
|
||||
});
|
||||
}
|
||||
}),
|
||||
svg(move || {
|
||||
let config = config.get();
|
||||
config
|
||||
.symbol_svg(&SymbolKind::FILE)
|
||||
.unwrap_or_else(|| config.ui_svg(LapceIcons::FILE))
|
||||
})
|
||||
.style(move |s| {
|
||||
let config = config.get();
|
||||
let size = config.ui.icon_size() as f32;
|
||||
s.min_width(size)
|
||||
.size(size, size)
|
||||
.margin_right(5.0)
|
||||
.color(
|
||||
config
|
||||
.symbol_color(&SymbolKind::FILE)
|
||||
.unwrap_or_else(|| {
|
||||
config
|
||||
.color(LapceColor::LAPCE_ICON_ACTIVE)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
label(move || format!("{:?}", path))
|
||||
.style(move |s| {
|
||||
s.margin_left(6.0).color(
|
||||
config.get().color(LapceColor::EDITOR_DIM),
|
||||
)
|
||||
})
|
||||
.into_any(),
|
||||
))
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.height(ui_line_height.get())
|
||||
.padding_left((level * 10) as f32)
|
||||
.items_center()
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
}),
|
||||
ReferenceLocation::Line { file_line, .. } => stack((container(
|
||||
label(move || format!("{} {}", file_line.position.line + 1, file_line.content))
|
||||
.style(move |s| {
|
||||
s.margin_left(6.0).color(
|
||||
config.get().color(LapceColor::EDITOR_DIM),
|
||||
)
|
||||
})
|
||||
.into_any(),
|
||||
)
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.height(ui_line_height.get())
|
||||
.padding_left((level * 20) as f32)
|
||||
.items_center()
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
}),))
|
||||
.on_click_stop({
|
||||
let window_tab_data = window_tab_data.clone();
|
||||
let position = file_line.position;
|
||||
move |_| {
|
||||
window_tab_data.common.internal_command.send(
|
||||
InternalCommand::JumpToLocation {
|
||||
location: EditorLocation {
|
||||
path: file_line.path.clone(),
|
||||
position: Some(
|
||||
crate::editor::location::EditorPosition::Position(
|
||||
position,
|
||||
),
|
||||
),
|
||||
scroll_offset: None,
|
||||
ignore_unconfirmed: false,
|
||||
same_editor_tab: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
}
|
||||
.style(move |s| {
|
||||
s.padding_right(5.0)
|
||||
.height(ui_line_height.get())
|
||||
.padding_left((level * 10) as f32)
|
||||
.items_center()
|
||||
.hover(|s| {
|
||||
s.background(
|
||||
config
|
||||
.get()
|
||||
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
|
||||
)
|
||||
.cursor(CursorStyle::Pointer)
|
||||
})
|
||||
})
|
||||
},
|
||||
).item_size_fixed(move || ui_line_height.get())
|
||||
.style(|s| s.flex_col().absolute().min_width_full()),
|
||||
)
|
||||
.style(|s| s.absolute().size_full())
|
||||
}
|
||||
|
||||
pub fn map_to_location(resp: Option<GotoImplementationResponse>) -> Vec<Location> {
|
||||
let Some(resp) = resp else {
|
||||
return Vec::new();
|
||||
};
|
||||
match resp {
|
||||
GotoImplementationResponse::Scalar(local) => {
|
||||
vec![local]
|
||||
}
|
||||
GotoImplementationResponse::Array(items) => items,
|
||||
GotoImplementationResponse::Link(items) => items
|
||||
.into_iter()
|
||||
.map(|x| Location {
|
||||
uri: x.target_uri,
|
||||
range: x.target_range,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_implementation_root(
|
||||
items: Vec<FileLine>,
|
||||
scope: Scope,
|
||||
) -> ReferencesRoot {
|
||||
let mut refs_map: HashMap<PathBuf, HashMap<u32, Reference>> = HashMap::new();
|
||||
for item in items {
|
||||
let entry = refs_map.entry(item.path.clone()).or_default();
|
||||
(*entry).insert(
|
||||
item.position.line,
|
||||
Reference::Line {
|
||||
location: ReferenceLocation::Line {
|
||||
file_line: item,
|
||||
view_id: ViewId::new(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut refs = Vec::new();
|
||||
for (path, items) in refs_map {
|
||||
let open = scope.create_rw_signal(true);
|
||||
let children = items
|
||||
.into_iter()
|
||||
.sorted_by(|x, y| x.0.cmp(&y.0))
|
||||
.map(|x| x.1)
|
||||
.collect();
|
||||
let ref_item = Reference::File {
|
||||
location: ReferenceLocation::File {
|
||||
open,
|
||||
path,
|
||||
view_id: ViewId::new(),
|
||||
},
|
||||
children,
|
||||
open,
|
||||
};
|
||||
refs.push(ref_item);
|
||||
}
|
||||
ReferencesRoot { children: refs }
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ReferencesRoot {
|
||||
pub(crate) children: Vec<Reference>,
|
||||
}
|
||||
|
||||
impl ReferencesRoot {
|
||||
pub fn total(&self) -> usize {
|
||||
let mut total = 0;
|
||||
for child in &self.children {
|
||||
total += child.total_len()
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
fn get_children(
|
||||
&self,
|
||||
next: &mut usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
level: usize,
|
||||
) -> Vec<(usize, usize, ReferenceLocation)> {
|
||||
let mut children = Vec::new();
|
||||
for child in &self.children {
|
||||
let child_children = child.get_children(next, min, max, level + 1);
|
||||
if !child_children.is_empty() {
|
||||
children.extend(child_children);
|
||||
}
|
||||
if *next > max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualVector<(usize, usize, ReferenceLocation)> for ReferencesRoot {
|
||||
fn total_len(&self) -> usize {
|
||||
self.total()
|
||||
}
|
||||
|
||||
fn slice(
|
||||
&mut self,
|
||||
range: std::ops::Range<usize>,
|
||||
) -> impl Iterator<Item = (usize, usize, ReferenceLocation)> {
|
||||
let min = range.start;
|
||||
let max = range.end;
|
||||
let children = self.get_children(&mut 0, min, max, 0);
|
||||
children.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Reference {
|
||||
File {
|
||||
location: ReferenceLocation,
|
||||
open: RwSignal<bool>,
|
||||
children: Vec<Reference>,
|
||||
},
|
||||
Line {
|
||||
location: ReferenceLocation,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ReferenceLocation {
|
||||
File {
|
||||
path: PathBuf,
|
||||
open: RwSignal<bool>,
|
||||
view_id: ViewId,
|
||||
},
|
||||
Line {
|
||||
file_line: FileLine,
|
||||
view_id: ViewId,
|
||||
},
|
||||
}
|
||||
|
||||
impl ReferenceLocation {
|
||||
pub fn view_id(&self) -> ViewId {
|
||||
match self {
|
||||
ReferenceLocation::File { view_id, .. } => *view_id,
|
||||
ReferenceLocation::Line { view_id, .. } => *view_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reference {
|
||||
pub fn location(&self) -> ReferenceLocation {
|
||||
match self {
|
||||
Reference::File { location, .. } => location.clone(),
|
||||
Reference::Line { location } => location.clone(),
|
||||
}
|
||||
}
|
||||
pub fn total_len(&self) -> usize {
|
||||
match self {
|
||||
Reference::File { children, .. } => {
|
||||
let mut total = 1;
|
||||
for child in children {
|
||||
total += child.total_len()
|
||||
}
|
||||
total
|
||||
}
|
||||
Reference::Line { .. } => 1,
|
||||
}
|
||||
}
|
||||
pub fn children(&self) -> Option<&Vec<Reference>> {
|
||||
match self {
|
||||
Reference::File { children, open, .. } => {
|
||||
if open.get() {
|
||||
return Some(children);
|
||||
}
|
||||
None
|
||||
}
|
||||
Reference::Line { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_children(
|
||||
&self,
|
||||
next: &mut usize,
|
||||
min: usize,
|
||||
max: usize,
|
||||
level: usize,
|
||||
) -> Vec<(usize, usize, ReferenceLocation)> {
|
||||
let mut children = Vec::new();
|
||||
if *next >= min && *next < max {
|
||||
children.push((*next, level, self.location()));
|
||||
} else if *next >= max {
|
||||
return children;
|
||||
}
|
||||
next.add_assign(1);
|
||||
if let Some(children_tmp) = self.children() {
|
||||
for child in children_tmp {
|
||||
let child_children = child.get_children(next, min, max, level + 1);
|
||||
if !child_children.is_empty() {
|
||||
children.extend(child_children);
|
||||
}
|
||||
if *next > max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ pub enum PanelKind {
|
||||
Search,
|
||||
Problem,
|
||||
Debug,
|
||||
CallHierarchy,
|
||||
DocumentSymbol,
|
||||
References,
|
||||
Implementation,
|
||||
}
|
||||
|
||||
impl PanelKind {
|
||||
@@ -27,6 +31,10 @@ impl PanelKind {
|
||||
PanelKind::Search => LapceIcons::SEARCH,
|
||||
PanelKind::Problem => LapceIcons::PROBLEM,
|
||||
PanelKind::Debug => LapceIcons::DEBUG,
|
||||
PanelKind::CallHierarchy => LapceIcons::TYPE_HIERARCHY,
|
||||
PanelKind::DocumentSymbol => LapceIcons::DOCUMENT_SYMBOL,
|
||||
PanelKind::References => LapceIcons::REFERENCES,
|
||||
PanelKind::Implementation => LapceIcons::IMPLEMENTATION,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +47,20 @@ impl PanelKind {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn default_position(&self) -> PanelPosition {
|
||||
match self {
|
||||
PanelKind::Terminal => PanelPosition::BottomLeft,
|
||||
PanelKind::FileExplorer => PanelPosition::LeftTop,
|
||||
PanelKind::SourceControl => PanelPosition::LeftTop,
|
||||
PanelKind::Plugin => PanelPosition::LeftTop,
|
||||
PanelKind::Search => PanelPosition::BottomLeft,
|
||||
PanelKind::Problem => PanelPosition::BottomLeft,
|
||||
PanelKind::Debug => PanelPosition::LeftTop,
|
||||
PanelKind::CallHierarchy => PanelPosition::BottomLeft,
|
||||
PanelKind::DocumentSymbol => PanelPosition::RightTop,
|
||||
PanelKind::References => PanelPosition::BottomLeft,
|
||||
PanelKind::Implementation => PanelPosition::BottomLeft,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
pub mod call_hierarchy_view;
|
||||
pub mod data;
|
||||
pub mod debug_view;
|
||||
pub mod document_symbol;
|
||||
pub mod global_search_view;
|
||||
pub mod implementation_view;
|
||||
pub mod kind;
|
||||
pub mod plugin_view;
|
||||
pub mod position;
|
||||
pub mod problem_view;
|
||||
pub mod references_view;
|
||||
pub mod source_control_view;
|
||||
pub mod style;
|
||||
pub mod terminal_view;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user