Compare commits

...

458 Commits

Author SHA1 Message Date
Jakub Panek 95f7c53ff9 wip 2024-05-31 16:35:24 +02:00
Jakub Panek f2355aa0b8 decode percent-encoded URLs, move path_from_url to own module
fixes #2932
2024-05-29 22:52:42 +02:00
Jakub Panek 21192b6ec1 fix formatting 2024-05-29 21:13:33 +02:00
Jakub Panek f2ab022735 fix parsing URLs for LSPs that send it URL encoded
Add unhandled encoding of drive letter

Fixes #2954
Fixes #1942
2024-05-29 21:06:42 +02:00
Jakub Panek 4bc49aa8db fix: update schannel
fixes #3290
closes #3291
2024-05-29 14:56:08 +02:00
dependabot[bot] 9ed68160e1 chore(deps): bump serde_json from 1.0.107 to 1.0.117
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.107 to 1.0.117.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.107...v1.0.117)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 12:49:57 +00:00
dependabot[bot] 65a99d5945 chore(deps): bump parking_lot from 0.12.1 to 0.12.3
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.12.1 to 0.12.3.
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.12.1...0.12.3)

---
updated-dependencies:
- dependency-name: parking_lot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 12:49:29 +00:00
dependabot[bot] 3251d3d05c chore(deps): bump libc from 0.2.153 to 0.2.155
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.153 to 0.2.155.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.153...0.2.155)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 12:47:40 +00:00
dependabot[bot] 3ee00cebb8 chore(deps): bump thiserror from 1.0.58 to 1.0.61
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.61.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.61)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 12:47:30 +00:00
dependabot[bot] 2e601e50ab chore(deps): bump chrono from 0.4.34 to 0.4.38
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.34 to 0.4.38.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.34...v0.4.38)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-29 12:46:41 +00:00
Jakub Panek af72732768 fix launching default terminal profile when showing panel
fixes #2375
2024-05-28 09:36:27 +02:00
Jakub Panek 6a64a5331e remove unnecessary "Settings" suffix, hide terminal profiles from settings 2024-05-26 21:01:17 +02:00
Dongdong Zhou 9d016feb26 bump floem 2024-05-21 21:54:07 +01:00
dependabot[bot] 11f36a7038 chore(deps): bump pulldown-cmark from 0.10.2 to 0.10.3 (#3223)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-11 12:50:04 +02:00
dependabot[bot] 0ddc216245 chore(deps): bump unicode-width from 0.1.11 to 0.1.12 (#3226)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-11 01:39:53 +02:00
dependabot[bot] 0ff10e346f chore(deps): bump bytemuck from 1.14.3 to 1.15.0 (#3187)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-11 01:08:53 +02:00
Dongdong Zhou 5263087173 Update README.md 2024-05-10 21:46:34 +01:00
Dongdong Zhou 3f69ff4c18 stop palette scroll to bubble up 2024-05-10 21:39:44 +01:00
Jakub Panek c604f513ae fix: reimplement path un/install for macos (#3255)
Fixes #3254
2024-05-10 15:23:34 +02:00
Dongdong Zhou c9f5f8fd20 proxy gzip overwrite file 2024-05-09 22:46:36 +01:00
Dongdong Zhou 985b07cca2 bump floem 2024-05-08 23:01:42 +01:00
Jakub Panek 09065bb7af fix: remove tree-sitter-yaml
fixes #3251
2024-05-07 22:07:45 +02:00
Jakub Panek 1004b7fbf3 fix: use default profile when spawning terminal panel
fixes #3244
2024-05-05 23:58:47 +02:00
Jakub Panek cbca98e108 fix clippy lints (#3237) 2024-05-02 17:39:17 +02:00
Jakub Panek 75cfa069aa add additional logging for environment loading 2024-05-01 21:43:25 +02:00
Jakub Panek 3dc59aeb27 fix: remove unsupported highlight for cpp 2024-05-01 15:44:30 +02:00
Dongdong Zhou 43bdb124e3 hover doesn't propagate wheel event
Fix #3219
2024-04-29 21:24:19 +01:00
dependabot[bot] 6fd83c49bc chore(deps): bump hashbrown from 0.14.3 to 0.14.5
Bumps [hashbrown](https://github.com/rust-lang/hashbrown) from 0.14.3 to 0.14.5.
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.14.3...v0.14.5)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 18:19:07 +00:00
Jakub Panek e9a87c0b01 fix: use windows subsystem to prevent console running
fixes #2813
2024-04-29 19:43:37 +02:00
Jakub Panek 8985acd81b fix: log error with path when it cannot be parsed to Url 2024-04-28 12:40:43 +02:00
Dongdong Zhou 0bc1c97575 rename preview_editor_width to palette_width 2024-04-27 22:36:50 +01:00
Jakub Panek 36627a93ec ci: fix fedora glob 2024-04-26 11:13:59 +02:00
Jakub Panek 42b219e642 add fedora builds (#3204) 2024-04-26 09:23:48 +02:00
MinusGix b066d164d3 Fallback to default theme if key is wrong (#3203) 2024-04-25 22:17:25 +02:00
Dongdong Zhou d390b5870c Add cursor affinity for diagnostic phantom text
Fix #3202
2024-04-25 14:16:58 +01:00
Jakub Panek f49ff95eee docs: add pkgconf just in case 2024-04-25 11:38:21 +02:00
Dongdong Zhou 8531d7abe9 bump version to 0.4.0
Release / tagname (push) Has been cancelled
Release / windows (push) Has been cancelled
Release / linux (push) Has been cancelled
Release / deb (debian, bookworm) (push) Has been cancelled
Release / deb (debian, bullseye) (push) Has been cancelled
Release / deb (ubuntu, focal) (push) Has been cancelled
Release / deb (ubuntu, jammy) (push) Has been cancelled
Release / deb (ubuntu, lunar) (push) Has been cancelled
Release / deb (ubuntu, mantic) (push) Has been cancelled
Release / Build lapce-proxy for aarch64 (push) Has been cancelled
Release / Build lapce-proxy for x86_64 (push) Has been cancelled
Release / macos (push) Has been cancelled
Release / publish (push) Has been cancelled
2024-04-24 21:32:06 +01:00
Dongdong Zhou 7c99eca99e update changelog 2024-04-24 21:27:45 +01:00
Dongdong Zhou 49f4f50dfd make code action and completion clickable 2024-04-24 21:16:07 +01:00
Jakub Panek ab52dbcd57 docs: use openssl system lib
fixes #3198
2024-04-24 15:17:47 +02:00
Dongdong Zhou a234ae75e8 update screenshot 2024-04-23 21:27:05 +01:00
Dongdong Zhou abd6688fc5 Focus key bindings input when open
Fix #3174
2024-04-23 21:25:03 +01:00
Dongdong Zhou e2ec4234b7 reimplement settings dropdown
fix #2904
2024-04-23 21:22:13 +01:00
dependabot[bot] cc90d1e0bc chore(deps): bump structdesc from a6a2b8a to 47d8201
Bumps [structdesc](https://github.com/lapce/structdesc) from `a6a2b8a` to `47d8201`.
- [Commits](https://github.com/lapce/structdesc/compare/a6a2b8ab65d43475238f889736c4aa5dfff4500a...47d8201fb13240166f4d842c718d63c1e24f0236)

---
updated-dependencies:
- dependency-name: structdesc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-23 03:09:20 +00:00
Jakub Panek 7ebdf7f5bb fix fmt 2024-04-23 03:38:26 +02:00
Jakub Panek 468d416704 log target lapce_core=debug, try to unload library on failure and log the error 2024-04-23 03:11:53 +02:00
Dongdong Zhou 46f29719dd restore unsaved buffer 2024-04-22 22:20:58 +01:00
dependabot[bot] d94668cea5 chore(deps): bump smallvec from 1.13.1 to 1.13.2
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.13.1 to 1.13.2.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.13.1...v1.13.2)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 18:12:07 +00:00
Dongdong Zhou 78b90a4ccd Update README.md 2024-04-20 21:14:34 +01:00
Dongdong Zhou 9fb61d0a72 remember panel section open status 2024-04-19 22:20:52 +01:00
dependabot[bot] 3466d465f8 chore(deps): bump h2 from 0.3.24 to 0.3.26
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 10:57:11 +00:00
Dongdong Zhou 5e2a31e09f rework diagnostics 2024-04-18 21:59:20 +01:00
ifengqi d75cc7bf12 Fix the issue where the copied text might be empty. (#3178)
Right-clicking in the terminal directly copy the selected text without needing to retrieve it.
2024-04-18 16:45:58 +01:00
Jakub Panek 9ab8f6d919 fix: add terminal profile example 2024-04-18 17:28:57 +02:00
Dongdong Zhou ad0a719e02 bump floem 2024-04-17 22:39:24 +01:00
Jakub Panek f0d1fba177 fix: use pre-installed wix (#3172) 2024-04-17 22:33:01 +02:00
Jakub Panek 38a6961987 fix: update wix toolset 2024-04-17 19:50:43 +02:00
Dongdong Zhou 659e4ce870 bump floem 2024-04-15 22:07:13 +01:00
dependabot[bot] c1ce13d1ce chore(deps): bump libloading from 0.7.4 to 0.8.1
Bumps [libloading](https://github.com/nagisa/rust_libloading) from 0.7.4 to 0.8.1.
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.7.4...0.8.1)

---
updated-dependencies:
- dependency-name: libloading
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 20:07:35 +00:00
dependabot[bot] f5cdedc0b2 chore(deps): bump serde from 1.0.190 to 1.0.197
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.190 to 1.0.197.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.190...v1.0.197)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 18:50:15 +00:00
dependabot[bot] 6b38f76161 chore(deps): bump crossbeam-channel from 0.5.11 to 0.5.12
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.11 to 0.5.12.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.11...crossbeam-channel-0.5.12)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 18:24:37 +00:00
dependabot[bot] c2321a963b chore(deps): bump regex from 1.10.2 to 1.10.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.2 to 1.10.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.2...1.10.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 18:16:03 +00:00
Kevin King fc40496e51 Implement ShowHover command to support modal hover keybind (#3158)
Hides the hover popup on any move command.
2024-04-15 08:40:58 +01:00
Jakub Panek 6fa0f4c4b9 docs: use cargo install 2024-04-11 10:21:07 +02:00
Jakub Panek e189ead0c1 fix: upgrade pulldown-cmark (#3156) 2024-04-11 10:19:08 +02:00
dependabot[bot] 76c6aa24f7 chore(deps): bump reqwest from 0.11.24 to 0.11.27
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.24 to 0.11.27.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.24...v0.11.27)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 07:36:33 +00:00
dependabot[bot] 10f12074f7 chore(deps): bump open from 5.0.1 to 5.1.2
Bumps [open](https://github.com/Byron/open-rs) from 5.0.1 to 5.1.2.
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v5.0.1...v5.1.2)

---
updated-dependencies:
- dependency-name: open
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 07:17:05 +00:00
dependabot[bot] 8d32ebd43a chore(deps): bump backtrace from 0.3.69 to 0.3.71
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.69 to 0.3.71.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.69...0.3.71)

---
updated-dependencies:
- dependency-name: backtrace
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 07:09:45 +00:00
dependabot[bot] fe93911306 chore(deps): bump rayon from 1.8.0 to 1.10.0
Bumps [rayon](https://github.com/rayon-rs/rayon) from 1.8.0 to 1.10.0.
- [Changelog](https://github.com/rayon-rs/rayon/blob/main/RELEASES.md)
- [Commits](https://github.com/rayon-rs/rayon/compare/rayon-core-v1.8.0...rayon-core-v1.10.0)

---
updated-dependencies:
- dependency-name: rayon
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 07:07:31 +00:00
dependabot[bot] d219fec54f chore(deps): bump tar from 0.4.38 to 0.4.40
Bumps [tar](https://github.com/alexcrichton/tar-rs) from 0.4.38 to 0.4.40.
- [Commits](https://github.com/alexcrichton/tar-rs/compare/0.4.38...0.4.40)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 06:51:31 +00:00
Jakub Panek cbf4c78a98 fix: upgrade nucleo, remove unused futures dependency (#3155) 2024-04-11 08:50:47 +02:00
Jakub Panek 2dca24cbdc fix: remove unused tokio dependency (#3153) 2024-04-10 21:00:35 +02:00
Dongdong Zhou 3efd11a8ae fix terminal bg drawing (#3152) 2024-04-09 22:16:44 +01:00
Jakub Panek cb968ab523 fix: make keymap failure warn level 2024-04-05 09:16:04 +02:00
Jakub Panek f64ed5bd86 fix: use trace level for keypress 2024-04-05 09:14:22 +02:00
Jakub Panek 9f4c589e9d fix: lockfile 2024-04-05 07:14:28 +02:00
Jakub Panek 54d9a7b7b2 fix: switch to tree-sitter-json crate 2024-04-05 07:09:31 +02:00
Dongdong Zhou f9742f9e0f no modal for local doc 2024-04-02 22:11:50 +01:00
Dongdong Zhou d66b3c7cff create editor modal from config 2024-04-02 21:54:19 +01:00
Dongdong Zhou e8e0d4e0c2 treat altgr as modifier (#3143) 2024-04-01 21:11:49 +01:00
MinusGix 2202343cdc Fix terminal default profile saving incorrectly (#3142) 2024-04-01 05:34:42 +02:00
dependabot[bot] bc617f8995 chore(deps): bump tree-sitter-javascript from 0.20.3 to 0.20.4 (#3112)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 01:36:50 +02:00
dependabot[bot] 819bdcb972 chore(deps): bump hashbrown from 0.14.2 to 0.14.3 (#3039)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 01:36:27 +02:00
MinusGix cfc88c434b Update floem (#3134) 2024-04-01 00:49:20 +02:00
Jakub Panek 7b5ef24132 ci: refactor deb builds, link openssl statically (#3133) 2024-03-31 20:21:16 +02:00
Long0x0 8507ad9eb5 fix settings page navigation issue (#3130) 2024-03-30 15:17:47 +01:00
MinusGix 350aa48b6f Update floem (#3128) 2024-03-29 22:03:42 +00:00
MinusGix 40e1631b1d Fix theme fallback (#3127) 2024-03-29 20:39:25 +00:00
Dongdong Zhou 0f9090640b special keymap parsing for + 2024-03-29 14:46:04 +00:00
Philippe Cholet 6c68a01761 Two small itertools simplifications (#3126) 2024-03-29 13:25:08 +01:00
dependabot[bot] 21e6719af2 Merge pull request #3062 from lapce/dependabot/cargo/futures-0.3.30 2024-03-29 05:27:37 +00:00
dependabot[bot] cf81aaad3c chore(deps): bump futures from 0.3.28 to 0.3.30
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.28 to 0.3.30.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.28...0.3.30)

---
updated-dependencies:
- dependency-name: futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 21:51:52 +00:00
Dongdong Zhou 39ef70df34 import KeyEventExtModifierSupplement from floem 2024-03-28 21:48:35 +00:00
Dongdong Zhou cb8b8b9728 Click icon event propagation (#3124)
* swap sled to plain text file for db

* add no click icon
2024-03-28 21:43:35 +00:00
Dongdong Zhou bfa61fd2e2 swap sled to plain text file for db (#3123) 2024-03-28 21:35:48 +00:00
MinusGix 571485f1a6 Update floem for editor styling (#3122) 2024-03-28 21:12:40 +00:00
Jakub Panek 9cd3bcc611 fix: dont panic on missing colour 2024-03-28 13:41:33 +01:00
Dongdong Zhou 3ac55f9508 Keyboard (#3119)
* return error if can't deserialize json

* in progress

* clean up

* clippy fix
2024-03-27 22:54:57 +00:00
MinusGix aabb7f27f2 Editors struct (#3118)
* Use Editors struct to cleanup usage

* Add editors to Editors appropriately
2024-03-27 22:40:30 +00:00
Dongdong Zhou 383fdf8e1a return error if can't deserialize json 2024-03-27 21:51:52 +00:00
Jakub Panek b99a208471 fix: add remote disconnect button 2024-03-27 01:03:35 +01:00
Dongdong Zhou 29cc333460 update notariation env key name 2024-03-26 22:50:54 +00:00
Dongdong Zhou 823651e7dd bump alacritty_terminal 2024-03-26 22:42:39 +00:00
Dongdong Zhou b3aa8aefa6 don't exit stdio rpc if msg wrong format 2024-03-26 22:42:26 +00:00
Dongdong Zhou 80ccc709df Downgrade config to 0.13.4
Syntax colors are affected by this bug

https://github.com/mehcode/config-rs/issues/531

where enumMember is lowercased when
parsing the config
2024-03-26 21:24:53 +00:00
Dongdong Zhou 14dd01bd2a Add scroll command to the internal commands 2024-03-26 21:24:53 +00:00
Dongdong Zhou cf1a33398a editor copy include more information 2024-03-26 21:24:53 +00:00
Dongdong Zhou 8b9a94d617 change notarize action 2024-03-26 21:24:53 +00:00
Jakub Panek 6a02307208 fix: add event propagation to certain buttons (#3115) 2024-03-26 18:34:39 +01:00
dependabot[bot] 354a23c470 Merge pull request #3038 from lapce/dependabot/cargo/polling-3.5.0 2024-03-25 17:23:56 +00:00
jmhuang 716125ddb6 new function of terminal: double left-click to select the text, then right-click to copy (#3106) 2024-03-25 14:12:03 +00:00
Jakub Panek ed098beac2 fix: improve observability for plugins (#3104) 2024-03-25 13:49:34 +01:00
dependabot[bot] 5dceb340dc chore(deps): bump polling from 3.2.0 to 3.5.0
Bumps [polling](https://github.com/smol-rs/polling) from 3.2.0 to 3.5.0.
- [Release notes](https://github.com/smol-rs/polling/releases)
- [Changelog](https://github.com/smol-rs/polling/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/polling/compare/v3.2.0...v3.5.0)

---
updated-dependencies:
- dependency-name: polling
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 09:48:08 +00:00
Jakub Panek 3873b88b10 fix: bump config (#3103) 2024-03-25 10:45:26 +01:00
jmhuang f2b49fda2f add selection function for terminal (#3105) 2024-03-25 09:32:05 +00:00
Jakub Panek 6ca7df9e91 fix: bump dependencies, remove indexmap/parking_lot duplicates (#3101) 2024-03-25 08:55:45 +00:00
Jakub Panek b9f1382f85 fix: typo in workspace dependency 2024-03-25 01:16:41 +01:00
MinusGix 987bfb8b28 Update floem (#3099) 2024-03-24 18:52:33 +00:00
MinusGix 19cdabc84b Add text color if file is changed according to source control (#3096) 2024-03-24 18:52:18 +00:00
MinusGix d8dc78c7f2 Add setting to toggle open editors section (#3095) 2024-03-24 17:57:06 +00:00
Jakub Panek 78e3c52ba2 docs: remove matrix button (#3094) 2024-03-24 17:56:44 +00:00
Jakub Panek 51a56ac085 style: format Cargo manifests (#3092) 2024-03-24 17:56:31 +00:00
Jakub Panek 18e63a6345 fix: switch back to async-std for rfd 2024-03-22 13:49:58 +01:00
Long0x0 1024a98506 fix panel resizing bug (#3089)
* fix panel resizing bug

Issue #2832.

* Revert "fix panel resizing bug"

This reverts commit 6111696814c5973908c941eff9490ba784c38e6a.

* fix panel resizing bug

Issue #2832.

---------

Co-authored-by: long <gmchenlong@gmail.com>
2024-03-22 08:50:39 +00:00
Jakub Panek 16e0775297 fix: update msrv (#3013) 2024-03-21 16:52:41 +01:00
Jakub Panek 30cb9f58dd fix(clippy): fix new clippy lints (#3090) 2024-03-21 16:51:51 +01:00
jmhuang d7092d775a add underline for hyperlink (#3087)
* add underline for hyperlink

* update according to clippy
2024-03-20 22:38:47 +00:00
jmhuang 900591b774 preliminary implement: Jump from terminal prompt to the corresponding… (#3084)
* preliminary implement: Jump from terminal prompt to the corresponding file location.

* allow "too_many_arguments"

* optimized code
2024-03-19 21:12:15 +00:00
MinusGix f4a89ead78 Foldable panel sections (#3086) 2024-03-19 08:38:53 +00:00
MinusGix b9d21e6003 Simplify settings (#3082) 2024-03-17 21:05:26 +00:00
MinusGix a7b76414e4 Add plugin-path for loading plugin(s) from specific folder(s) (#3076) 2024-03-17 19:10:21 +00:00
MinusGix 2ea1c8aba2 Add tooltip coloring to settings.toml (#3077) 2024-03-15 07:33:05 +01:00
MinusGix 750b12242c Implement arbitrary theme variables (#3069) 2024-03-12 21:06:40 +00:00
MinusGix 8127fc25ce Fix scrolling in file explorer (#3068) 2024-03-12 13:53:38 +00:00
MinusGix 21c714d3f0 Update floem (#3060) 2024-03-11 21:45:55 +00:00
MinusGix 48ca2364e5 Move line up/down keybinds in insert to macos (#3055) 2024-03-09 07:59:12 +00:00
MinusGix 85de48d3f7 Focus on app_view when creating the view (#3054) 2024-03-08 22:52:47 +00:00
MinusGix f5e862c78c Tooltips (#3053)
* Minor changes

* Add tooltips to various places
2024-03-08 22:30:33 +00:00
MinusGix 70cee3b1b0 Implement line-ending switching (#3047) 2024-03-07 21:18:55 +00:00
dependabot[bot] 3397ce7523 Merge pull request #3042 from lapce/dependabot/cargo/mio-0.8.11 2024-03-05 06:03:37 +00:00
dependabot[bot] 628b5eef71 chore(deps): bump mio from 0.8.9 to 0.8.11
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.9 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.9...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 05:35:21 +00:00
dependabot[bot] e4bef9d8d9 Merge pull request #3032 from lapce/dependabot/cargo/tree-sitter-cpp-0.20.5 2024-03-04 06:52:06 +00:00
dependabot[bot] c289599826 chore(deps): bump tree-sitter-cpp from 0.20.4 to 0.20.5
Bumps [tree-sitter-cpp](https://github.com/tree-sitter/tree-sitter-cpp) from 0.20.4 to 0.20.5.
- [Commits](https://github.com/tree-sitter/tree-sitter-cpp/compare/v0.20.4...v0.20.5)

---
updated-dependencies:
- dependency-name: tree-sitter-cpp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 05:48:43 +00:00
dependabot[bot] 849ac6753f Merge pull request #3017 from lapce/dependabot/cargo/bytemuck-1.14.3 2024-03-04 05:36:19 +00:00
dependabot[bot] 4f77923124 Merge pull request #3019 from lapce/dependabot/cargo/sha2-0.10.8 2024-03-04 05:35:56 +00:00
dependabot[bot] 4e5ed2bef0 Merge pull request #3018 from lapce/dependabot/cargo/git2-0.18.2 2024-03-04 05:35:38 +00:00
dependabot[bot] fbe37339ae Merge pull request #3021 from lapce/dependabot/cargo/thiserror-1.0.55 2024-03-04 05:35:13 +00:00
MinusGix 25045a2213 Focus on app_view so that find receives keypresses (#3031) 2024-03-03 13:42:52 +00:00
geekvest 52de8302c1 fix some comments (#3030)
Signed-off-by: geekvest <cuimoman@sohu.com>
2024-03-03 13:42:19 +00:00
MinusGix 54e6682755 Remaining file explorer context menu entries (#3026)
* Comments + minor style changes

* Remove unused id signal

* Initial reimpl of File Explorer context menu options

* Add New File/Directory & Duplicate

* Simplify handling of naming states

This makes so that rather than being separate state machines with their own functions, they just share the state logic, thus avoiding the need to rewrite the same functions N times.

* Deduplicate explorer children creation

* Allow opening context menu anywhere in file explorer

* Fix clippy
2024-02-28 12:47:29 +00:00
dependabot[bot] 1eefaf49ba chore(deps): bump sha2 from 0.10.7 to 0.10.8
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.7 to 0.10.8.
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.7...sha2-v0.10.8)

---
updated-dependencies:
- dependency-name: sha2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 15:44:22 +00:00
dependabot[bot] 995e4fdcc6 chore(deps): bump thiserror from 1.0.50 to 1.0.55
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.50 to 1.0.55.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.50...1.0.55)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 15:44:12 +00:00
dependabot[bot] a8b6e2d324 chore(deps): bump git2 from 0.18.1 to 0.18.2
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.18.1 to 0.18.2.
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.18.1...git2-0.18.2)

---
updated-dependencies:
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 15:44:04 +00:00
dependabot[bot] c94607e5db chore(deps): bump bytemuck from 1.14.0 to 1.14.3
Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.14.0 to 1.14.3.
- [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md)
- [Commits](https://github.com/Lokathor/bytemuck/compare/v1.14.0...v1.14.3)

---
updated-dependencies:
- dependency-name: bytemuck
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-27 15:43:56 +00:00
Jakub Panek 631eebfb56 fix: bump softbuffer to 0.3.4 to update drm 2024-02-27 15:42:22 +00:00
Jakub Panek d2dcf4cf98 chore(deps): bump clap from 3 to 4 (#3015) 2024-02-25 21:08:43 +01:00
dependabot[bot] a5863e54b6 Merge pull request #2997 from lapce/dependabot/cargo/globset-0.4.14 2024-02-25 14:08:25 +00:00
Jakub Panek 1ed42aef3d fix: update softbuffer (#3014) 2024-02-25 15:05:48 +01:00
dependabot[bot] 159c9cfc46 chore(deps): bump smallvec from 1.11.1 to 1.13.1 (#3009)
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.11.1 to 1.13.1.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.11.1...v1.13.1)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 14:41:46 +01:00
dependabot[bot] b41cf0ca4e chore(deps): bump walkdir from 2.3.2 to 2.4.0 (#3010)
Bumps [walkdir](https://github.com/BurntSushi/walkdir) from 2.3.2 to 2.4.0.
- [Commits](https://github.com/BurntSushi/walkdir/compare/2.3.2...2.4.0)

---
updated-dependencies:
- dependency-name: walkdir
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 14:41:24 +01:00
Jakub Panek 17bc13d4fa build: fix ubuntu builds 2024-02-23 10:57:19 +01:00
Jakub Panek 32d68b8a67 fix: announce support for workspace_folders in lsp/psp (#3005) 2024-02-23 04:30:32 +01:00
Jakub Panek 890dd0afe7 Allow Dependabot to open more PRs (#2670) 2024-02-22 23:00:17 +01:00
Jakub Panek 37b2773bac fix: include .desktop and logo in debian packages (#3002) 2024-02-22 21:35:31 +01:00
dependabot[bot] e6a66cab8d chore(deps): bump globset from 0.4.13 to 0.4.14
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.13 to 0.4.14.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.13...globset-0.4.14)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-22 20:17:39 +00:00
Jakub Panek 49e4df0e89 fix: move globset to workspace deps (#3000) 2024-02-22 21:14:51 +01:00
a.e 8d1c65df98 chore(deps): bump floem to 341afd (#2999)
* chore: bump floem to 39e7eb

* chore(deps): bump floem to `341afd`

* fix: handle panics
2024-02-22 08:24:28 +00:00
Jakub Panek cea8a10a12 look one dir lower for binaries when publishing 2024-02-21 01:42:03 +01:00
Jakub Panek a970532d47 fix simple nightly tag 2024-02-20 23:39:53 +01:00
Jakub Panek 1a22debaf5 add building packages for linux distros (#2989) 2024-02-20 21:32:42 +01:00
dependabot[bot] 43b54d022e Merge pull request #2994 from lapce/dependabot/cargo/base64-0.21.7 2024-02-19 19:33:03 +00:00
dependabot[bot] 797e42ac3a Merge pull request #2993 from lapce/dependabot/cargo/url-2.5.0 2024-02-19 19:32:55 +00:00
dependabot[bot] 9ce38338fe chore(deps): bump base64 from 0.21.5 to 0.21.7
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.21.5 to 0.21.7.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.5...v0.21.7)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 17:38:23 +00:00
dependabot[bot] e9c3b96596 chore(deps): bump url from 2.3.1 to 2.5.0
Bumps [url](https://github.com/servo/rust-url) from 2.3.1 to 2.5.0.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.3.1...v2.5.0)

---
updated-dependencies:
- dependency-name: url
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 17:38:09 +00:00
Josh Triplett 15ec2fe5c9 Cargo.toml: Add license to metadata (#2986)
The README lists the license as Apache-2.0, but the `Cargo.toml` doesn't
contain license metadata. Add the license to `Cargo.toml` as well.
2024-02-18 21:13:48 +00:00
Dongdong Zhou 50b7152983 bump floem 2024-02-17 14:55:06 +00:00
dependabot[bot] 45951733ee Merge pull request #2973 from lapce/dependabot/cargo/chrono-0.4.34 2024-02-15 15:38:49 +00:00
dependabot[bot] f9731ba74b Merge pull request #2971 from lapce/dependabot/cargo/globset-0.4.13 2024-02-15 15:37:48 +00:00
dependabot[bot] 37f2160a13 chore(deps): bump chrono from 0.4.22 to 0.4.34
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.22 to 0.4.34.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.22...v0.4.34)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 15:11:38 +00:00
dependabot[bot] d405ef2e2f chore(deps): bump globset from 0.4.9 to 0.4.13
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.9 to 0.4.13.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.9...globset-0.4.13)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 15:10:38 +00:00
dependabot[bot] 59cc301050 Merge pull request #2968 from lapce/dependabot/cargo/tree-sitter-cpp-0.20.4 2024-02-15 13:26:08 +00:00
dependabot[bot] 2bb82948cb Merge pull request #2967 from lapce/dependabot/cargo/notify-5.2.0 2024-02-15 13:24:41 +00:00
dependabot[bot] f021173496 Merge pull request #2970 from lapce/dependabot/cargo/open-5.0.1 2024-02-15 13:24:09 +00:00
dependabot[bot] 29ddb212fe Merge pull request #2969 from lapce/dependabot/cargo/reqwest-0.11.24 2024-02-15 13:23:07 +00:00
dependabot[bot] 4872588c80 chore(deps): bump open from 5.0.0 to 5.0.1
Bumps [open](https://github.com/Byron/open-rs) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: open
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:56:26 +00:00
dependabot[bot] 9f944091e3 chore(deps): bump reqwest from 0.11.22 to 0.11.24
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.22 to 0.11.24.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.22...v0.11.24)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:56:05 +00:00
dependabot[bot] ed3f599360 chore(deps): bump tree-sitter-cpp from 0.20.0 to 0.20.4
Bumps [tree-sitter-cpp](https://github.com/tree-sitter/tree-sitter-cpp) from 0.20.0 to 0.20.4.
- [Commits](https://github.com/tree-sitter/tree-sitter-cpp/compare/v0.20.0...v0.20.4)

---
updated-dependencies:
- dependency-name: tree-sitter-cpp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:55:51 +00:00
dependabot[bot] f6328a72c0 chore(deps): bump notify from 5.0.0 to 5.2.0
Bumps [notify](https://github.com/notify-rs/notify) from 5.0.0 to 5.2.0.
- [Release notes](https://github.com/notify-rs/notify/releases)
- [Changelog](https://github.com/notify-rs/notify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/notify-rs/notify/commits)

---
updated-dependencies:
- dependency-name: notify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:55:38 +00:00
dependabot[bot] 993774f396 Merge pull request #2963 from lapce/dependabot/cargo/grep-regex-0.1.11 2024-02-15 12:51:59 +00:00
dependabot[bot] 63d15f870a Merge pull request #2966 from lapce/dependabot/cargo/crossbeam-channel-0.5.11 2024-02-15 12:51:30 +00:00
dependabot[bot] 7b45a7504f Merge pull request #2964 from lapce/dependabot/cargo/tokio-1.36.0 2024-02-15 12:49:48 +00:00
dependabot[bot] 823ff968d7 Merge pull request #2965 from lapce/dependabot/cargo/tree-sitter-c-0.20.8 2024-02-15 12:48:55 +00:00
dependabot[bot] f75143f23e chore(deps): bump crossbeam-channel from 0.5.8 to 0.5.11
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.8 to 0.5.11.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.8...crossbeam-channel-0.5.11)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:21:43 +00:00
dependabot[bot] 3099fff025 chore(deps): bump tree-sitter-c from 0.20.6 to 0.20.8
Bumps [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) from 0.20.6 to 0.20.8.
- [Release notes](https://github.com/tree-sitter/tree-sitter-c/releases)
- [Commits](https://github.com/tree-sitter/tree-sitter-c/compare/v0.20.6...v0.20.8)

---
updated-dependencies:
- dependency-name: tree-sitter-c
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:21:10 +00:00
dependabot[bot] f8fa97b46b chore(deps): bump tokio from 1.33.0 to 1.36.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.33.0 to 1.36.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.33.0...tokio-1.36.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:20:44 +00:00
dependabot[bot] 98fe5c0739 chore(deps): bump grep-regex from 0.1.10 to 0.1.11
Bumps [grep-regex](https://github.com/BurntSushi/ripgrep) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-regex-0.1.10...grep-regex-0.1.11)

---
updated-dependencies:
- dependency-name: grep-regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 12:20:30 +00:00
dependabot[bot] 35f1d9f258 Merge pull request #2962 from lapce/dependabot/cargo/once_cell-1.19.0 2024-02-15 12:09:56 +00:00
dependabot[bot] 7acc4f5cda chore(deps): bump once_cell from 1.18.0 to 1.19.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.18.0 to 1.19.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 11:43:33 +00:00
dependabot[bot] 3c5713e013 Merge pull request #2961 from lapce/dependabot/cargo/tree-sitter-javascript-0.20.3 2024-02-15 11:41:40 +00:00
dependabot[bot] 6bc8dd0f3e Merge pull request #2959 from lapce/dependabot/cargo/bumpalo-3.14.0 2024-02-15 11:41:34 +00:00
dependabot[bot] 4422838795 Merge pull request #2960 from lapce/dependabot/cargo/slotmap-1.0.7 2024-02-15 11:40:38 +00:00
dependabot[bot] 8044cdadba chore(deps): bump tree-sitter-javascript from 0.20.1 to 0.20.3
Bumps [tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) from 0.20.1 to 0.20.3.
- [Commits](https://github.com/tree-sitter/tree-sitter-javascript/compare/v0.20.1...v0.20.3)

---
updated-dependencies:
- dependency-name: tree-sitter-javascript
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 11:16:24 +00:00
dependabot[bot] d49d2f1f93 chore(deps): bump slotmap from 1.0.6 to 1.0.7
Bumps [slotmap](https://github.com/orlp/slotmap) from 1.0.6 to 1.0.7.
- [Changelog](https://github.com/orlp/slotmap/blob/master/RELEASES.md)
- [Commits](https://github.com/orlp/slotmap/compare/v1.0.6...v1.0.7)

---
updated-dependencies:
- dependency-name: slotmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 11:15:38 +00:00
dependabot[bot] 013ca94186 chore(deps): bump bumpalo from 3.11.0 to 3.14.0
Bumps [bumpalo](https://github.com/fitzgen/bumpalo) from 3.11.0 to 3.14.0.
- [Changelog](https://github.com/fitzgen/bumpalo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fitzgen/bumpalo/compare/3.11.0...3.14.0)

---
updated-dependencies:
- dependency-name: bumpalo
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 11:13:53 +00:00
dependabot[bot] 5dc17de070 Merge pull request #2765 from lapce/dependabot/cargo/dyn-clone-1.0.16 2024-02-15 10:49:39 +00:00
dependabot[bot] 2b97c5d618 Merge pull request #2956 from lapce/dependabot/cargo/openssl-0.10.63 2024-02-15 10:43:57 +00:00
dependabot[bot] d448fe5f66 Merge pull request #2957 from lapce/dependabot/cargo/h2-0.3.24 2024-02-15 10:43:15 +00:00
dependabot[bot] fcccc0fb9d Merge pull request #2958 from lapce/dependabot/cargo/unsafe-libyaml-0.2.10 2024-02-15 10:41:25 +00:00
Jakub Panek 7258f38f62 add windows-sys, add error popup on panic, bring back panic hook to logfile (#2686) 2024-02-15 10:27:15 +00:00
dependabot[bot] 0c82823218 chore(deps): bump dyn-clone from 1.0.10 to 1.0.16
Bumps [dyn-clone](https://github.com/dtolnay/dyn-clone) from 1.0.10 to 1.0.16.
- [Release notes](https://github.com/dtolnay/dyn-clone/releases)
- [Commits](https://github.com/dtolnay/dyn-clone/compare/1.0.10...1.0.16)

---
updated-dependencies:
- dependency-name: dyn-clone
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 10:23:22 +00:00
dependabot[bot] 47d8a18bda Merge pull request #2719 from lapce/dependabot/cargo/flate2-1.0.28 2024-02-15 10:21:41 +00:00
dependabot[bot] 81a70dd545 Merge pull request #2721 from lapce/dependabot/cargo/grep-searcher-0.1.11 2024-02-15 10:20:57 +00:00
dependabot[bot] 26cbf7081d Merge pull request #2720 from lapce/dependabot/cargo/toml_edit-0.20.2 2024-02-15 10:20:21 +00:00
dependabot[bot] ad5f47defb Merge pull request #2952 from lapce/dependabot/cargo/rustix-0.37.27 2024-02-15 10:17:03 +00:00
dependabot[bot] b739064959 chore(deps): bump unsafe-libyaml from 0.2.9 to 0.2.10
Bumps [unsafe-libyaml](https://github.com/dtolnay/unsafe-libyaml) from 0.2.9 to 0.2.10.
- [Release notes](https://github.com/dtolnay/unsafe-libyaml/releases)
- [Commits](https://github.com/dtolnay/unsafe-libyaml/compare/0.2.9...0.2.10)

---
updated-dependencies:
- dependency-name: unsafe-libyaml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 10:15:29 +00:00
dependabot[bot] e759fa88b4 chore(deps): bump h2 from 0.3.21 to 0.3.24
Bumps [h2](https://github.com/hyperium/h2) from 0.3.21 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.21...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 10:15:23 +00:00
dependabot[bot] 2dcc07466c chore(deps): bump openssl from 0.10.42 to 0.10.63
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.42 to 0.10.63.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.42...openssl-v0.10.63)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 10:15:15 +00:00
Arnaud Fernandes 0b567128fe feat: focus search input when opening settings (#2949)
* feat: focus search input when opening settings

* fix: now allow to enter chars

* refactor: use dedicated method

* style: cargo fmt

* fix: PR feedback
2024-02-15 10:13:24 +00:00
a.e 4f5318d519 Feat: add files_exclude option (Fixes #811) (#2955)
* feat: add `files_exclude` option

* chore: update feat changelog (#2955)

* fix: clippy warning
2024-02-15 09:12:19 +00:00
dependabot[bot] 94a6f8d374 Merge pull request #2951 from lapce/dependabot/cargo/openssl-src-111.28.11.1.1w 2024-02-13 11:11:56 +00:00
dependabot[bot] 0d5869fe96 chore(deps): bump rustix from 0.37.23 to 0.37.27
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.23 to 0.37.27.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.37.23...v0.37.27)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-13 11:03:29 +00:00
dependabot[bot] 2b9b9f7ba4 Merge pull request #2950 from lapce/dependabot/cargo/libgit2-sys-0.16.21.7.2 2024-02-13 11:02:49 +00:00
dependabot[bot] 73e8ec44f9 chore(deps): bump openssl-src from 111.22.0+1.1.1q to 111.28.1+1.1.1w
Bumps [openssl-src](https://github.com/alexcrichton/openssl-src-rs) from 111.22.0+1.1.1q to 111.28.1+1.1.1w.
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

---
updated-dependencies:
- dependency-name: openssl-src
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-13 10:43:44 +00:00
dependabot[bot] 0ae52f5b31 chore(deps): bump libgit2-sys from 0.16.1+1.7.1 to 0.16.2+1.7.2
Bumps [libgit2-sys](https://github.com/rust-lang/git2-rs) from 0.16.1+1.7.1 to 0.16.2+1.7.2.
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/commits)

---
updated-dependencies:
- dependency-name: libgit2-sys
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-13 10:33:41 +00:00
Dongdong Zhou e3cc7601a8 sanitise window size when reading from db 2024-02-12 21:30:47 +00:00
MinusGix 6e7aa1c5ab Floem Editor (#2909)
* Initial sketch of floem-editor

* Very basic phantom

* Basic wrapping + layout styling

* More phantom, more styling, more view code

* Line height, more drawing code

* More drawing logic

* Move to separate crate

* Add example; fixes

* Partial reimpl of Lapce's Document

* Downcasting Document; add basic EditorData impl

* Progress on implementing run_command

* More progress on run_command

* Editor container view

* Hook up modal and smart tab

* Receive char

* Add clicking support

* Add Ctrl+{Home,End}

* Remove usage of buffer in Cursor::yank

* Bunch of editor -> new editor functions impl

* Scroll commands and other bits

* Make buildable

* Cursor blink; remove vestiges of sticky header

* Impl compute screen lines for new doc

* Replace existing modules with floem-editor versions

* Shift over to using floem-editor (broken)

* Remove uses of original doc

* Rename doc2 -> doc

* Don't force Rc to allow swapping out Editor signals

* Add Styling::id; various minor changes

* Fix updating Lines

* Hook up styling

* Deduplicate many pieces of paint logic

* Make indentation on newline configurable

* Impl scroll commands

* Add clippy changes from non-rebased commit

* Simplify+improve view update logic; fix style sharing bug

* Stop returning SyntaxEdit on edits

This was needed so that we can avoid depending on tree-sitter when extracting the buffer + editing logic out to floem-editor
This actually makes it cheaper in the cases where we don't need the edits.
However we are factoring the delta twice now. It would be better to not do that.

* Remove Syntax usage in Buffer

* Move syntax/util.rs, because syntax won't be in floem-editor-core

* Move all editor logic out of lapce-core and into floem-editor-core

* Remove lapce deps from floem-editor

* Fix warnings

* Fix various floem-editor TODOs

* Make cursor blink at the correct times

* Move last_movement onto floem-editor

* Rename core Editor -> Action to avoid name collisions

* Avoid crash where line is out of bounds while loading file

* Return iterative ropes with deltas

* Put off some todos; Reimpl some missing select cmds

* Dispose of editor on cleanup

* Move floem-editor to floem; various changes from that

* Update to use latest floem

* Update floem; Pass cache rev
2024-02-12 21:23:15 +00:00
Geoffrey Gordon Ashbrook bdc6351e6d adding fedora linux instructions to package-manager doc file (#2928)
* Update installing-with-package-manager.md

* Update installing-with-package-manager.md

* Update installing-with-package-manager.md

* Update installing-with-package-manager.md

* Update installing-with-package-manager.md

* Update installing-with-package-manager.md
2024-01-25 12:10:37 +00:00
bbrabbitt 2c08177d9b feat(editor): bracket colorization (#2835)
* feat(editor): bracket highlight

style: apply clippy fix

* feat(editor): use tree-sitter parser, add config and theme colors for colorization

* fix: clear bracket_pos to prevent miscolored characters

* fix: add fallback colors if theme does not provide bracket colors

chore: fmt

* fix: utf-8 char will cause crash

fix: brackets in string interpolation

* rename config; add get_base_color instead of using TERMINAL

* remove fallback

* fix: pass config to Document::new

* update colorization code

* cargo fmt

* feat(editor): use tree-sitter parser, add config and theme colors for colorization

* fix: clear bracket_pos to prevent miscolored characters

* fix: utf-8 char will cause crash

fix: brackets in string interpolation

* rename config; add get_base_color instead of using TERMINAL

* remove fallback

* fix: pass config to Document::new

* update colorization code

* cargo fmt

* add bracket colorization limit

* cargo fmt and clippy

* merge

* merge

* merge to latest doc.rs

* update doc.rs

* change from to_string to String::new

* set colorization to off by default
2024-01-18 22:50:01 +01:00
Benno Straub 9bddebda16 Improve search in keyboard shortcuts overview (#2918)
* improve search in keyboard shortcuts overview

* Update CHANGELOG.md
2024-01-13 15:41:07 +00:00
jmhuang f3d1719ca7 Fixed the issue where the plugin folder couldn't be deleted when unin… (#2915)
* Fixed the issue where the plugin folder couldn't be deleted when uninstalling a plugin, due to the WASI instance not being dropped.

* Fixed the issue where the plugin folder couldn't be deleted when uninstalling a plugin, due to the WASI instance not being dropped(or operating systems not releasing immediately).

* Optimized the code according to Clippy's suggestions.
2024-01-13 15:39:08 +00:00
Dongdong Zhou 8d51772988 fix some display bugs to do with screen line cache 2024-01-12 21:56:26 +00:00
Dongdong Zhou 2eb17a2150 editor view layout needs to fill in cache 2024-01-10 21:20:03 +00:00
Alexandru Grecu 86981f1624 Left panel file ordering fixed (#2912)
* Solved an issue

* solved the issue, kept human_sort

* ran cargo fmt
2024-01-10 09:45:19 +00:00
Dongdong Zhou 4cc31611d8 move update_document_completion out of completion update 2024-01-06 15:00:00 +00:00
riverbl 5652efc39b Implement comparing files in the diff editor (#2910)
The user can either:

* Select the files to compare by right clicking on them in the file explorer.

* Invoke the `diff_files` command and select the files to compare from the palette.

Fix double clicking on an unconfirmed diff editor tab not confirming it.
2024-01-06 14:33:00 +00:00
Dongdong Zhou 65a73e0d24 update_document_completion can trigger window_origin
So we need to move it out of update() so that it doesn't
cause the already borrowed crash
2024-01-05 23:15:36 +00:00
Dongdong Zhou 9424a3336a bump floem 2024-01-05 21:25:02 +00:00
Alexandre Leblanc 83120215ad Fix/git commit silently fail missing username email (#2878)
* fix: Commit on freshly initialized repository.

Fixes #2779

* fix: Prompt error message when Git user.name or user.email is missing.

Fixes #1277.

* Update CHANGELOG.md

---------

Co-authored-by: Alexandre Leblanc <a.leblanc@kitai.dev>
2024-01-02 09:44:52 +00:00
Jakub Panek e1f3892729 fix: wsl (#2877) 2024-01-02 09:44:30 +00:00
Alexandre Leblanc 1d585b2bb5 fix: detect git changes when no commit in repository (#2872)
* fix: Display untracked files on a fresh git repository.

Issue #2779.

Known issue: files without history are displaying text-less diff.

* fix: Display untracked files on a fresh git repository.

Issue #2779.

Known issue: files without history are displaying text-less diff.

* fix: clippy warnings

* fix: clippy errors

---------

Co-authored-by: Alexandre Leblanc <a.leblanc@kitai.dev>
2023-12-30 18:09:43 +00:00
riverbl a4d805e77f Implement path renaming from the file explorer (#2863)
* Implement UI for renaming files

Implement functionality for renaming files and directories in lapce-app, accessible by right clicking the item in the file explorer and selecting "Rename...".

Backend functionality for file renaming in lapce-proxy was already present and is unchanged.

If the rename fails, the name is not updated in the file explorer and no error is reported.

Open editors are not notified of the rename. If the user saves the contents of an already open editor after performing a rename, the file will be saved in its original location.

* Improve file renaming functionality

* When renaming, lapce-proxy is now queried on each keystroke to see if the new path is valid and errors are reported to the user.

* If a rename operation fails, the rename text box stays open and the error is reported to the user.

* When renaming a file, any buffers or editors the renamed file is open in are updated to use the new path.

* When renaming a directory, any buffers or editors a file the renamed directory is an ancestor of is open in are updated to use the new path.

* Non existing ancestor directories of the new path are created on rename.
2023-12-27 16:15:33 +00:00
Jakub Panek 0e0355122a docs: cleanup distro specific info (#2865) 2023-12-27 15:59:51 +00:00
Jakub Panek 5c09aa731d fix: add cmakelists to cmake lang (#2864) 2023-12-27 04:52:43 +01:00
Jakub Panek 1f040084e4 build(deps): lock lsp-types to patch versions only (#2860) 2023-12-24 07:28:29 +01:00
MinusGix f7d2f4ba86 Remove LapceConfig from do_insert (#2849)
* Remove LapceConfig requirement on do_insert

* Remove lsp max_severity DiagnosticSeverity from phantom text
2023-12-13 09:13:36 +00:00
MinusGix b7d394408a get_color -> color; return copies of Color (#2848) 2023-12-13 09:12:49 +00:00
Dongdong Zhou 048c5103e2 bump floem 2023-12-11 21:43:54 +00:00
MinusGix 286d2d463d Document no longer has common; Add line styling (#2847) 2023-12-11 21:43:36 +00:00
MinusGix e9435735f2 Implement inline completion (#2846) 2023-12-11 19:08:05 +00:00
MinusGix f1a96106c1 Doc backend (#2836)
* Initial basic save backend

* Trait extension, move history to backend

* Move diagnostics to Document backend

* Move inlay hints to Document backend

* Move completion lens to Document backend

* Move code actions to Document backend

* Move syntax/semantic highlighting to Document backend

* Move breakpoints to Document backend

* Rename ProxyBackend -> DocBackend

* Update floem
2023-12-09 19:06:54 +00:00
Jakub Panek 7a330513af fix: hide inline grammars (#2840) 2023-12-08 18:49:12 +01:00
Jakub Panek c6cf1db4a8 fix using default terminal profile (#2834) 2023-12-07 02:26:46 +01:00
MinusGix 87e0fc06f1 Line wrap (#2723)
* Line-wrap impl

* Fix paint normal selection around linewrapping and phantom text

* Fix linewise selection

* Maybe fix scroll bug

* Fix breakpoint positioning

* Clippy fixes

* Docs + Fixes + Cleanup

* Make phantom text before_col more indirect

This will be useful once we stop aggressively computing the full PhantomText whenever it is accessed.

* Make over-monomorphizations over TextLayoutProvider less likely

* Fix bench
2023-12-05 14:14:37 +00:00
Dongdong Zhou cb96e5ac3f bump version
Release / tagname (push) Has been cancelled
Release / windows (push) Has been cancelled
Release / linux (push) Has been cancelled
Release / Build lapce-proxy for aarch64 (push) Has been cancelled
Release / Build lapce-proxy for x86_64 (push) Has been cancelled
Release / macos (push) Has been cancelled
Release / publish (push) Has been cancelled
2023-12-04 21:13:24 +00:00
Dongdong Zhou 2cf6fa12cb add ruby tree sitter queries 2023-12-04 21:11:08 +00:00
Dongdong Zhou ede1159b7f fix editor scroll 2023-11-30 20:54:47 +00:00
Dongdong Zhou 111219cd49 bump floem 2023-11-29 21:58:29 +00:00
Lukas Lihotzki 95063498a3 bump floem and use floem::Clipboard (#2815) 2023-11-29 17:23:53 +00:00
Dimitri Tabatadze c68a00e19a feat: implement Format Document command (#2823) 2023-11-29 09:00:52 +00:00
Dimitri Tabatadze d8452a1e49 fix: save without formatting now calls save (#2820) 2023-11-28 11:14:17 +00:00
MinusGix 592cbc7b6f create_dir_all when saving (#2806) 2023-11-21 09:04:49 +00:00
riverbl 9009c97916 Reworked cursor rendering, fixing several bugs (#2803)
* Block cursors were rendered with a fixed width, rather than the width of the current character, when after an inlay hint and using a variable width font.

* In visual normal mode, the selection highlight didn't extend past the end of the current line when the cursor was past the end of the line.

* In visual normal mode, if there was an inlay hint after the end of the current line and the cursor was past the end of the line, the cursor was rendered after the inlay hint.

* In visual normal and block modes, when selecting backwards to the first character after an inlay hint, the selection highlight extended leftward of the cursor to include the inlay hint.

* When there were multiple cursors on the same line, only one was rendered.

* The selection highlight for visual block mode excluded the rightmost column when selecting right and up or left and down.

* When in visual block mode, when the cursor was to the left of the anchor column of the selection and past the end of the line, the cursor wasn't rendered.
2023-11-20 19:23:49 +00:00
riverbl e3de6269a4 Fix invalid unicode filenames not being displayed (#2804)
Fix filenames containing invalid unicode not being displayed in the breadcrumbs, open editors list or tab titles
2023-11-20 19:23:14 +00:00
riverbl f26e045978 Fix non_canonical_partial_ord_impl clippy lints (#2798)
* Reimplement PartialOrd in terms of Ord for some types to fix non_canonical_partial_ord_impl (previously called incorrect_partial_ord_impl_on_ord_type) clippy lints

* Fix potential incorrect order or panic when comparing FileNodeViewData objects containing invalid unicode
2023-11-20 12:07:59 +00:00
Tv 8a137c5b2c Add explicit static lifetime to consts (#2793)
This removes 219 warnings with Rust 1.74.0.
All changes are from `cargo fix`.
See https://github.com/rust-lang/rust/issues/115010
2023-11-19 21:21:14 +00:00
Dongdong Zhou c7e0474ebf move cursor blink timer to window 2023-11-15 21:15:36 +00:00
tk 8dc41a0df6 update the language in the status bar (#2783) 2023-11-14 21:33:38 +00:00
Dongdong Zhou a2ffa24d1c bump floem 2023-11-13 21:04:46 +00:00
Zoxc b7cf8b03f8 Add UI inspector command (#2782)
* Add UI inspector command

* Use `get_untracked`
2023-11-13 19:56:00 +00:00
Zoxc da290bf563 Bump Floem (#2778)
* Bump Floem

* Add a `default_window_config` method
2023-11-12 15:59:54 +00:00
Dongdong Zhou bbe431a162 run.toml on remote fix 2023-11-08 21:38:41 +00:00
Elmeri Keitaanranta d3e8ea8c6a Auto-surround config (#2764)
* wrap selected text with corresponding char fix

* auto-surround config
2023-11-08 21:38:32 +00:00
Jakub Panek de2d7b11e1 fix language name used in status bar (#2724)
* fix language name used in status bar

* use new name() func

* simplify to string conversion

* fix format
2023-11-06 09:37:14 +01:00
MinusGix 07040dab7f Don't mark nonexistent files as read only (#2754) 2023-11-04 17:47:36 +00:00
Dongdong Zhou e574e9c89b fix FocusText view 2023-11-04 15:04:21 +00:00
Dongdong Zhou 6766930df5 bump floem 2023-11-03 20:26:13 +00:00
dependabot[bot] e4ebafca03 chore(deps): bump toml_edit from 0.19.14 to 0.20.2
Bumps [toml_edit](https://github.com/toml-rs/toml) from 0.19.14 to 0.20.2.
- [Commits](https://github.com/toml-rs/toml/compare/v0.19.14...v0.20.2)

---
updated-dependencies:
- dependency-name: toml_edit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 22:23:04 +00:00
Dongdong Zhou a636ad0436 display visual line and visual block mode 2023-11-02 22:20:18 +00:00
Dongdong Zhou 983d230393 bump floem 2023-11-02 20:40:05 +00:00
Dongdong Zhou 79325cddfa bump floem 2023-11-02 20:20:25 +00:00
Dongdong Zhou 1435dc89fd fix version matching on lapce-proxy 2023-11-01 20:47:50 +00:00
Dongdong Zhou d02ab6f866 don't panic if log file can't be created 2023-11-01 20:47:50 +00:00
Jakub Panek 94d55375c6 update screenshot (#2744) 2023-11-01 17:22:53 +00:00
Dongdong Zhou c49e281422 bump version
Release / tagname (push) Has been cancelled
Release / windows (push) Has been cancelled
Release / linux (push) Has been cancelled
Release / Build lapce-proxy for aarch64 (push) Has been cancelled
Release / Build lapce-proxy for x86_64 (push) Has been cancelled
Release / macos (push) Has been cancelled
Release / publish (push) Has been cancelled
2023-10-31 21:13:43 +00:00
Dongdong Zhou 73414962b1 pty read should retry until it's empty 2023-10-31 21:04:35 +00:00
Dongdong Zhou b4a6ec5b04 pty token is different on windows 2023-10-30 20:58:17 +00:00
Dongdong Zhou c03153ba5e Update CHANGELOG.md 2023-10-30 20:27:45 +00:00
Dongdong Zhou 71a71a1905 download tree sitter grammars 2023-10-30 20:15:56 +00:00
Dongdong Zhou 980b025886 update toml queries 2023-10-28 07:43:09 +01:00
dependabot[bot] 0b707bbc77 chore(deps): bump grep-searcher from 0.1.10 to 0.1.11
Bumps [grep-searcher](https://github.com/BurntSushi/ripgrep) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-searcher-0.1.10...grep-searcher-0.1.11)

---
updated-dependencies:
- dependency-name: grep-searcher
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 02:35:44 +00:00
dependabot[bot] 0e90d87e23 chore(deps): bump flate2 from 1.0.24 to 1.0.28
Bumps [flate2](https://github.com/rust-lang/flate2-rs) from 1.0.24 to 1.0.28.
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.24...1.0.28)

---
updated-dependencies:
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 02:33:00 +00:00
dependabot[bot] d9094d926c Merge pull request #2717 from lapce/dependabot/cargo/open-5.0.0 2023-10-28 02:26:59 +00:00
dependabot[bot] 692c4c359b chore(deps): bump open from 3.2.0 to 5.0.0
Bumps [open](https://github.com/Byron/open-rs) from 3.2.0 to 5.0.0.
- [Release notes](https://github.com/Byron/open-rs/releases)
- [Changelog](https://github.com/Byron/open-rs/blob/main/changelog.md)
- [Commits](https://github.com/Byron/open-rs/compare/v3.2.0...v5.0.0)

---
updated-dependencies:
- dependency-name: open
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 01:30:40 +00:00
dependabot[bot] d7806565a1 Merge pull request #2713 from lapce/dependabot/cargo/git2-0.18.1 2023-10-28 01:28:52 +00:00
dependabot[bot] 87b13c987b Merge pull request #2716 from lapce/dependabot/cargo/smallvec-1.11.1 2023-10-28 01:21:27 +00:00
Jakub Panek 25aae633c4 ci: add vendor tarball (#2709) 2023-10-28 03:20:17 +02:00
dependabot[bot] 1f1020c3f9 Merge pull request #2714 from lapce/dependabot/cargo/regex-1.10.2 2023-10-28 01:06:37 +00:00
dependabot[bot] 6f87c66f3f Merge pull request #2715 from lapce/dependabot/cargo/zip-0.6.6 2023-10-28 01:04:20 +00:00
dependabot[bot] 76f6b78835 chore(deps): bump smallvec from 1.10.0 to 1.11.1
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.10.0 to 1.11.1.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.10.0...v1.11.1)

---
updated-dependencies:
- dependency-name: smallvec
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 00:19:52 +00:00
dependabot[bot] b55cdac039 chore(deps): bump zip from 0.6.4 to 0.6.6
Bumps [zip](https://github.com/zip-rs/zip) from 0.6.4 to 0.6.6.
- [Changelog](https://github.com/zip-rs/zip/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip/commits)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 00:19:22 +00:00
dependabot[bot] fac0fd3bd8 chore(deps): bump regex from 1.7.0 to 1.10.2
Bumps [regex](https://github.com/rust-lang/regex) from 1.7.0 to 1.10.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.7.0...1.10.2)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 00:19:03 +00:00
dependabot[bot] c12429f401 chore(deps): bump git2 from 0.17.2 to 0.18.1
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.17.2 to 0.18.1.
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/0.17.2...git2-0.18.1)

---
updated-dependencies:
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 00:18:43 +00:00
dependabot[bot] bdae83292f Merge pull request #2711 from lapce/dependabot/cargo/tree-sitter-c-0.20.6 2023-10-28 00:16:02 +00:00
dependabot[bot] bd432eeccc Merge pull request #2706 from lapce/dependabot/cargo/indexmap-2.0.2 2023-10-28 00:11:41 +00:00
dependabot[bot] 04b8281dd8 Merge pull request #2712 from lapce/dependabot/cargo/base64-0.21.5 2023-10-28 00:06:49 +00:00
dependabot[bot] 460582929f Merge pull request #2705 from lapce/dependabot/cargo/rayon-1.8.0 2023-10-27 23:31:31 +00:00
dependabot[bot] 64b2e9cb7e chore(deps): bump base64 from 0.21.4 to 0.21.5
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.21.4 to 0.21.5.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.21.4...v0.21.5)

---
updated-dependencies:
- dependency-name: base64
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 23:18:48 +00:00
dependabot[bot] db782db9b0 chore(deps): bump tree-sitter-c from 0.20.2 to 0.20.6
Bumps [tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) from 0.20.2 to 0.20.6.
- [Release notes](https://github.com/tree-sitter/tree-sitter-c/releases)
- [Commits](https://github.com/tree-sitter/tree-sitter-c/compare/v0.20.2...v0.20.6)

---
updated-dependencies:
- dependency-name: tree-sitter-c
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 23:18:34 +00:00
dependabot[bot] eace7c16cf chore(deps): bump indexmap from 2.0.0 to 2.0.2
Bumps [indexmap](https://github.com/bluss/indexmap) from 2.0.0 to 2.0.2.
- [Changelog](https://github.com/bluss/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/bluss/indexmap/compare/2.0.0...2.0.2)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 23:18:09 +00:00
dependabot[bot] 5ccc13bd03 Merge pull request #2704 from lapce/dependabot/cargo/unicode-width-0.1.11 2023-10-27 23:15:40 +00:00
dependabot[bot] b57e2fa49c chore(deps): bump rayon from 1.7.0 to 1.8.0
Bumps [rayon](https://github.com/rayon-rs/rayon) from 1.7.0 to 1.8.0.
- [Changelog](https://github.com/rayon-rs/rayon/blob/master/RELEASES.md)
- [Commits](https://github.com/rayon-rs/rayon/compare/rayon-core-v1.7.0...rayon-core-v1.8.0)

---
updated-dependencies:
- dependency-name: rayon
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 22:41:51 +00:00
Dongdong Zhou 0ceafc53b3 bump floem 2023-10-27 23:06:43 +01:00
Jakub Panek 59510d7c22 update wasmtime (#2710)
* update wasmtime

* update backtrace

* fix type

* update tokio
2023-10-27 22:50:58 +01:00
Dongdong Zhou 114fdd379a new terminal tab button 2023-10-27 22:47:28 +01:00
Dongdong Zhou 1ef665b881 language fixes 2023-10-27 22:01:09 +01:00
Dongdong Zhou 8c85108f8b theme color settings 2023-10-27 21:09:13 +01:00
Dongdong Zhou ebc01a4367 editor pointer leave to reset hover 2023-10-26 21:41:18 +01:00
dependabot[bot] 948fa96cd5 Merge pull request #2703 from lapce/dependabot/cargo/grep-matcher-0.1.6 2023-10-26 20:30:14 +00:00
dependabot[bot] 01faff6387 chore(deps): bump unicode-width from 0.1.10 to 0.1.11
Bumps [unicode-width](https://github.com/unicode-rs/unicode-width) from 0.1.10 to 0.1.11.
- [Commits](https://github.com/unicode-rs/unicode-width/compare/v0.1.10...v0.1.11)

---
updated-dependencies:
- dependency-name: unicode-width
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-26 19:40:49 +00:00
dependabot[bot] 7d98e8a28d chore(deps): bump grep-matcher from 0.1.5 to 0.1.6
Bumps [grep-matcher](https://github.com/BurntSushi/ripgrep) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-matcher-0.1.5...grep-matcher-0.1.6)

---
updated-dependencies:
- dependency-name: grep-matcher
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-26 19:40:36 +00:00
dependabot[bot] 72d84c0f6f Merge pull request #2680 from lapce/dependabot/cargo/interprocess-1.2.1 2023-10-26 11:02:56 +00:00
dependabot[bot] 27d932c1c2 chore(deps): bump interprocess from 1.1.1 to 1.2.1
Bumps [interprocess](https://github.com/kotauskas/interprocess) from 1.1.1 to 1.2.1.
- [Release notes](https://github.com/kotauskas/interprocess/releases)
- [Commits](https://github.com/kotauskas/interprocess/compare/1.1.1...1.2.1)

---
updated-dependencies:
- dependency-name: interprocess
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-26 10:08:25 +00:00
dependabot[bot] 3649e15ef0 Merge pull request #2702 from lapce/dependabot/cargo/tree-sitter-rust-0.20.4 2023-10-26 05:58:45 +00:00
dependabot[bot] d9fa230005 chore(deps): bump tree-sitter-rust from 0.20.1 to 0.20.4
Bumps [tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) from 0.20.1 to 0.20.4.
- [Commits](https://github.com/tree-sitter/tree-sitter-rust/compare/v0.20.1...v0.20.4)

---
updated-dependencies:
- dependency-name: tree-sitter-rust
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-26 05:16:59 +00:00
dependabot[bot] 0735f19ca0 Merge pull request #2701 from lapce/dependabot/cargo/hashbrown-0.14.0 2023-10-26 05:04:30 +00:00
dependabot[bot] 553da3a5b8 chore(deps): bump hashbrown from 0.12.3 to 0.14.0
Bumps [hashbrown](https://github.com/rust-lang/hashbrown) from 0.12.3 to 0.14.0.
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/compare/v0.12.3...v0.14.0)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-26 04:12:05 +00:00
dependabot[bot] a81f352b18 chore(deps): bump indexmap from 1.9.2 to 2.0.0 (#2694)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-26 06:10:43 +02:00
dependabot[bot] 2505c5b4eb Merge pull request #2685 from lapce/dependabot/cargo/cocoa-0.25.0 2023-10-26 03:47:48 +00:00
dependabot[bot] 5e17fa25d3 Merge pull request #2693 from lapce/dependabot/cargo/crossbeam-channel-0.5.8 2023-10-26 03:46:47 +00:00
Dongdong Zhou a090c788cf Merge pull request #2698 from MyGodItsFull0fStars/add-navigation-keybindings
Added navigation keybindings
2023-10-25 22:53:11 +01:00
MyGodItsFull0fStars 1a3389ca1b Added navigation keybindings 2023-10-25 23:42:43 +02:00
Dongdong Zhou b93bf952cd dap active term fix 2023-10-25 22:08:47 +01:00
Dongdong Zhou a6b22968ef plugin tab view 2023-10-25 21:27:56 +01:00
Dongdong Zhou eb316a939b bump floem 2023-10-24 22:37:57 +01:00
Dongdong Zhou 7a7931df85 plugin images 2023-10-24 21:18:47 +01:00
Dongdong Zhou 88580f2247 add the ability to register debugger types 2023-10-23 22:52:33 +01:00
dependabot[bot] 4514221191 chore(deps): bump crossbeam-channel from 0.5.6 to 0.5.8
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.6 to 0.5.8.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.6...crossbeam-channel-0.5.8)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 17:21:39 +00:00
Dongdong Zhou 548ca85c25 change breakpoint when edit 2023-10-21 22:22:21 +01:00
Dongdong Zhou 78ccc8bc1f Merge pull request #2690 from lapce/dap
terminal fixes
2023-10-20 22:31:52 +01:00
Dongdong Zhou 4f26af8668 Report terminal launch error 2023-10-20 22:31:15 +01:00
Dongdong Zhou 0d5fab5e67 remove which 2023-10-20 22:14:43 +01:00
Dongdong Zhou f5fbf24333 add program and args to prelaunch 2023-10-20 22:12:31 +01:00
Dongdong Zhou 82d30986e6 don't split terminal command 2023-10-20 21:40:13 +01:00
dependabot[bot] cdc0ae805a chore(deps): bump cocoa from 0.24.0 to 0.25.0
Bumps [cocoa](https://github.com/servo/core-foundation-rs) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/servo/core-foundation-rs/commits)

---
updated-dependencies:
- dependency-name: cocoa
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-20 19:59:44 +00:00
Dongdong Zhou 938ee96a8e Merge pull request #2608 from lapce/dap
DAP support
2023-10-20 20:56:44 +01:00
Dongdong Zhou 79fa5f3ffd fix linux build 2023-10-20 20:04:51 +01:00
Michael 640b7cfbea Terminal font height setting (#2683)
* calculate terminal font like editor

* terminal: calc contrast and switch colors accordingly

* Revert "terminal: calc contrast and switch colors accordingly"

This reverts commit d0c30d5c4b34f605d0f6745546003f8ce7b0d9ec.
2023-10-20 19:49:08 +01:00
Dongdong Zhou 36e908ecf2 add step into out 2023-10-20 19:47:28 +01:00
Dongdong Zhou 2cdd7af86f add debug step over 2023-10-19 21:27:38 +01:00
Dongdong Zhou 8e9bda9734 save breakpoints 2023-10-18 21:04:46 +01:00
Dongdong Zhou 515d6404cd Merge branch 'master' into dap 2023-10-18 12:10:55 +01:00
Dongdong Zhou 362a3bcf73 process breakpoint response 2023-10-17 21:18:58 +01:00
dependabot[bot] 99a4f13da3 Merge pull request #2681 from lapce/dependabot/cargo/wasi-experimental-http-wasmtime-21419eb 2023-10-15 10:56:20 +00:00
dependabot[bot] 9b09c3a5df Merge pull request #2679 from lapce/dependabot/cargo/arc-swap-1.6.0 2023-10-15 10:08:53 +00:00
dependabot[bot] 50e9a2170e chore(deps): bump wasi-experimental-http-wasmtime
Bumps [wasi-experimental-http-wasmtime](https://github.com/lapce/wasi-experimental-http) from `5c6d970` to `21419eb`.
- [Commits](https://github.com/lapce/wasi-experimental-http/compare/5c6d970fe0750932f76979678384bf1c5ab5be2e...21419eb785cb583ead180f25a9685fa16de7f326)

---
updated-dependencies:
- dependency-name: wasi-experimental-http-wasmtime
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-15 08:21:25 +00:00
dependabot[bot] 17b8d65664 chore(deps): bump arc-swap from 1.5.1 to 1.6.0
Bumps [arc-swap](https://github.com/vorner/arc-swap) from 1.5.1 to 1.6.0.
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: arc-swap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-15 08:20:41 +00:00
Jakub Panek f8caa220b9 fix: drop unused "which" dependency (#2669) 2023-10-15 09:27:19 +02:00
Dongdong Zhou f2750f812f fix children expand count 2023-10-10 20:16:10 +01:00
dependabot[bot] 626f7b30f1 Merge pull request #2665 from lapce/dependabot/cargo/reqwest-0.11.22 2023-10-10 06:24:32 +00:00
dependabot[bot] ced5417173 Merge pull request #2664 from lapce/dependabot/cargo/tree-sitter-javascript-0.20.1 2023-10-10 06:24:10 +00:00
dependabot[bot] cd7b9a6657 Merge pull request #2667 from lapce/dependabot/cargo/libc-0.2.149 2023-10-10 06:14:15 +00:00
Pengfei Jiang 3486db4543 feat: add thrift language id support (#2668)
Co-authored-by: 江鹏飞 <jiangpengfei.jiangpf@bytedance.com>
2023-10-10 08:12:56 +02:00
dependabot[bot] b9f6b54073 chore(deps): bump libc from 0.2.147 to 0.2.149
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.147 to 0.2.149.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.147...0.2.149)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 17:51:47 +00:00
dependabot[bot] 510dfd9fe6 chore(deps): bump reqwest from 0.11.12 to 0.11.22
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.12 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.12...v0.11.22)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 17:51:02 +00:00
dependabot[bot] ccfae88211 chore(deps): bump tree-sitter-javascript from 0.20.0 to 0.20.1
Bumps [tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) from 0.20.0 to 0.20.1.
- [Commits](https://github.com/tree-sitter/tree-sitter-javascript/compare/rust-0.20.0...v0.20.1)

---
updated-dependencies:
- dependency-name: tree-sitter-javascript
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 17:50:40 +00:00
Jakub Panek a760c24d9b Update CONTRIBUTING doc (#2659) 2023-10-08 22:39:46 +01:00
Jakub Panek 8f596cc6f1 Remove unnecessary document (#2658)
* Remove unnecessary document

* Remove dev contributing file
2023-10-08 22:39:17 +01:00
Jakub Panek 507e9e4de8 fix rest of status messages to have foreground colour (#2655) 2023-10-08 22:37:19 +01:00
dependabot[bot] c328533645 chore(deps): bump bytemuck from 1.13.1 to 1.14.0 (#2657)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 23:26:36 +02:00
dependabot[bot] 5b4c6c3868 chore(deps): bump trash from 2.1.5 to 3.0.6 (#2656)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 23:21:33 +02:00
dependabot[bot] 7a87fb9d34 chore(deps): bump clap from 3.2.22 to 3.2.25 (#2358)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-08 22:43:17 +02:00
Jakub Panek 98facf246c fix tab close button for good (#2654) 2023-10-08 21:10:37 +01:00
Dongdong Zhou 89c64856d4 Merge branch 'master' into dap 2023-10-08 20:30:41 +01:00
Jakub Panek 54b0850bd1 fix: make status text follow theme foreground key (#2653) 2023-10-08 20:21:03 +01:00
Auguste Rame f724016297 Send diagnostics capabilities for LSP (#2650) 2023-10-06 09:49:27 +02:00
Jakub Panek d1c1aaf302 Fix new clippy/rust lints (#2648)
* fix(clippy): clippy::unwrap-or-default

* fix: unwrap-or-else

* fix(clippy): clippy::redundant_locals

* fix(clippy): clippy::incorrect_partial_ord_impl_on_ord_type

* fix(clippy): clippy::redundant-guards
2023-10-05 21:46:37 +01:00
Dongdong Zhou d55a6ee9d7 add attribution for helix 2023-10-02 20:12:27 +01:00
Dongdong Zhou e295ce8d61 check if program is valid 2023-09-27 22:03:45 +01:00
Dongdong Zhou 3ab7637b70 Merge branch 'master' into dap 2023-09-27 21:37:20 +01:00
Dongdong Zhou 4ba0a9e54b change 100% to _full 2023-09-27 21:19:00 +01:00
Dongdong Zhou 7fc29b1baf change to new way of floem units 2023-09-27 21:07:55 +01:00
Dongdong Zhou fa48091520 keymap view improvement 2023-09-27 20:37:21 +01:00
Jakub Panek 1f62c4a3e5 feat: implement terminal profiles (#2467) 2023-09-26 21:14:05 +01:00
Victor 78e846ad89 Layout-agnostic shortcuts (#2634)
* Add support of hotkeys in layouts other than US English.

* Add support of non-alphabetic characters in keybindings
2023-09-26 21:07:08 +01:00
Jakub Panek 32dd88c299 fix margin when not using close button in tab (#2632) 2023-09-24 22:39:35 +01:00
riverbl 3cf1934484 Implement per cursor copy and paste (#2631)
* Implement per cursor copy and paste (#2625)

If the number of lines in the copy buffer is divisible by the number of selection regions, partition the copy buffer into groups of equal numbers of lines and paste one group at each selection region.

* Fix clippy lints

Fix clippy lints due to relaxing the bounds on `Buffer::edit`
2023-09-24 15:57:35 +01:00
Flix 7f8d7bdab9 Fix log files being empty (#2629) 2023-09-21 20:45:34 +01:00
Dongdong Zhou b288fcf02d in progress 2023-09-21 08:41:20 +01:00
Dongdong Zhou 4401d46528 breakpoint display 2023-09-21 08:41:20 +01:00
riverbl c539f7a205 Add support for highlighting enclosing brackets (#2622)
Highlight enclosing brackets when `highlight_matching_brackets` is enabled.

Draw lines delineating the enclosing scope when `highlight_scope_lines` is enabled.
2023-09-20 20:22:44 +01:00
Jakub Panek 83ca73b8f5 feat: allow changing position of close button in editor tabs (#2620) 2023-09-18 20:39:57 +01:00
MinusGix b45eee137b Send lsp (#2610)
* Make StartLspServer a request

* Add SendLsp{Notification,Request}
2023-09-18 20:39:42 +01:00
Dongdong Zhou 4154fe3831 don't update cursor if not needed 2023-09-13 22:01:56 +01:00
Dongdong Zhou 5bfc5975cd editor viewport update in compute_layout
this fixes an issue that sometimes it hangs on open a diff editor
2023-09-09 16:49:46 +01:00
Dongdong Zhou dd9a6cff42 fix palette theme 2023-09-09 15:02:48 +01:00
Jakub Panek 583e6713b9 feat: allow closing tab via mouse middle click (#2607) 2023-09-09 14:54:39 +01:00
Jakub Panek ec291ef33e ci: clean disk space between build and doctest on windows (#2606)
* ci: clean disk space between build and doctest on windows

* fix: typo
2023-09-09 13:28:39 +02:00
Dongdong Zhou c166dac44e no need drag resize for mac or custom title is off 2023-09-08 22:42:40 +01:00
Dongdong Zhou 6eba1eb02e bump floem 2023-09-08 22:34:40 +01:00
Dongdong Zhou 6fb6b58575 bump floem 2023-09-08 21:24:07 +01:00
Dongdong Zhou b9dae22447 bump floem 2023-09-08 21:11:17 +01:00
Dongdong Zhou a32a915788 implement custom title (#2603)
* implement custom title

* add drag resize
2023-09-08 20:28:07 +01:00
riverbl bf6f296235 Fix delete_line not deleting leading whitespace (#2602)
Fix delete_line not deleting leading whitespace on the first deleted line
2023-09-07 20:13:13 +01:00
Dongdong Zhou dc12d0f9a5 split resize 2023-09-06 21:20:08 +01:00
Dongdong Zhou 10c83e4320 panel resize (#2593)
* panel resize

* resize limits

* bump floem
2023-09-05 20:58:27 +01:00
theplasticbrick 5b177a7b8c Update building-from-source.md (#2596)
Updated the list of dependencies needed that were suggested. 
I was not able to compile using "cargo build --release" until I had installed perl-FindBin and perl-File-Compare using the standard "sudo dnf install"
I am using WSL2 with Fedora installed.
2023-09-04 20:58:40 +01:00
Dongdong Zhou a67d1920ac fix vim motion dw 2023-09-03 21:50:17 +01:00
Dongdong Zhou e368782880 add quit app 2023-09-03 21:25:10 +01:00
Dongdong Zhou 8d7a1f1b7f add completion_lens color for light theme 2023-09-02 22:45:07 +01:00
Dongdong Zhou fe6b048ec2 bump floem for linux context menu support 2023-09-02 22:24:08 +01:00
Dongdong Zhou 9f4b608072 make keyboard handling right 2023-09-02 14:14:36 +01:00
Dongdong Zhou 7a90970cea bump floem to fix zoom issue 2023-09-02 09:38:05 +01:00
Dongdong Zhou 7cfe5e6890 change the way of displaying file explorer (#2590) 2023-09-01 22:59:22 +01:00
Dongdong Zhou 5d1d3e8ab1 Reimplement file explorer (#2589)
* settings writing to file fix

* bump floem to fix font change display issue
2023-09-01 21:48:25 +01:00
Dongdong Zhou 9918d5efc0 settings writing to file fix (#2588) 2023-09-01 21:19:36 +01:00
Dongdong Zhou b584250ea7 make editor and doc all signals inside (#2587) 2023-09-01 20:33:19 +01:00
Dongdong Zhou 6ee9b8fcb7 show window message (#2583)
* show window message

* remove fuzzy matcher

* sort files
2023-08-31 20:13:10 +01:00
Dongdong Zhou 7b33526a53 split command for diff editor 2023-08-30 22:27:50 +01:00
Dongdong Zhou 5f5f4a1357 ime support (#2580) 2023-08-30 22:10:20 +01:00
Dongdong Zhou 6bd22fe837 add open editors (#2579)
* add open editors

* bump floem
2023-08-30 20:38:07 +01:00
Dongdong Zhou 36f27aec8f update dependency 2023-08-30 07:54:42 +01:00
Dongdong Zhou 76f1fc2991 update depedency 2023-08-30 07:45:37 +01:00
Dongdong Zhou 5e1a305471 switch to winit floem (#2566)
* switch to winit floem

* fix floem path

* bump floem

* fmt

* fix

* bump floem

* put system clipboard back

* fix keyboard

* add xorg-dev dependency for clipboard crate

* more dependecies

* bump floem
2023-08-29 23:18:33 +01:00
riverbl 27b7956bc8 Add new palette menu listing palette kinds (#2574)
* Add new palette menu listing palette kinds

* Revert implementation of Copy for CommandKind
2023-08-29 22:24:33 +01:00
Michael 558a951cf4 open files in new tab on middle click (#2576) 2023-08-29 20:29:14 +01:00
Wybe Westra a645336c18 Popout menus now appear beneath their buttons, instead of at (0, 0) (#2570) 2023-08-27 22:03:23 +01:00
riverbl ab8af33373 Allow binding mouse buttons to commands (#2573)
Allows specifying bindings that use mouseforward, mouseback and mousemiddle

Does not allow specifying bindings that use the primary two mouse buttons
2023-08-27 22:02:26 +01:00
Jakub Panek 56a98b9aaf fix proxy script, create and switch to lapce dir before download/unpack (#2571) 2023-08-27 15:53:23 +02:00
Jakub Panek ea36dbcae7 Fix clippy and rustfmt lint errors (#2568) 2023-08-27 15:03:58 +02:00
Jakub Panek da1a57c833 Merge pull request from GHSA-v7q3-c3cf-w238
* Merge pull request from GHSA-v7q3-c3cf-w238

* fix compilation errors and warnings

* format code, fix proxy version comparison, remove TAG from meta

* remove tmp dir

---------

Co-authored-by: MinusGix <MinusGix@gmail.com>
2023-08-27 15:02:26 +02:00
Jakub Panek 4b847be66a Revert "Change how proxy script is ran" (#2567) 2023-08-27 11:11:22 +02:00
MinusGix 520a6deb55 Merge pull request from GHSA-v7q3-c3cf-w238 2023-08-26 20:51:09 +01:00
Dongdong Zhou 13a97ce6d8 move to central mouse_hover_timer 2023-08-23 22:39:53 +01:00
Dongdong Zhou 30ffcebf3e add tab width 2023-08-23 22:23:27 +01:00
Dongdong Zhou a8758d9e6b hover support 2023-08-23 20:42:01 +01:00
Daniel McNab c05ef6f1d8 Add a feature to enable Wayland support (#2562) 2023-08-23 20:37:29 +01:00
Dongdong Zhou 5581fd8479 bump floem 2023-08-22 21:10:42 +01:00
Dongdong Zhou bd195c77a4 fix panic 2023-08-22 20:56:10 +01:00
Titaniumtown d4fe89f51a Add libxkbcommon-x11-devel as a dependency in lapce.spec (#2524) 2023-08-21 17:54:20 +02:00
Dongdong Zhou d6c143b12e bump floem 2023-08-20 20:24:32 +01:00
Dongdong Zhou 3a9d0739ad Blink cursor (#2558)
* in progress

* rougly working

* bump floem

* blink on focus change

* remove blink_interval commet
2023-08-20 13:46:47 +01:00
Dongdong Zhou 4d7553977f Auto save (#2557)
* auto save after delay

* don't format when auto save
2023-08-19 22:26:42 +01:00
Dongdong Zhou 635917f910 macos no titlebar (#2555)
* macos no titlebar

* add wayland dependency
2023-08-19 20:53:41 +01:00
Dongdong Zhou 9b064f5c81 add replace (#2553)
* add replace

* focus back to workbench when pointer down on search

* update changelog

* update changelog
2023-08-18 20:48:54 +01:00
Dongdong Zhou ddecf2c039 move editor tab (#2551)
* move on editor tab header

* move editor tab to new split
2023-08-17 21:00:41 +01:00
Ilya Volchenkov ad9a8e178a Fixed typo in the plugin view (#2546)
* Fixed typo in the plugin view

* added changelog entry

---------

Co-authored-by: Ilya Volchenkov <ivolchenkov@megaputer.ru>
Co-authored-by: Dongdong Zhou <dzhou121@gmail.com>
2023-08-16 22:18:42 +01:00
Jakub Panek 857e61de1e fix: update toml_edit and use toml crate for easy parsing (#2548) 2023-08-16 22:17:33 +01:00
MarcusGrass b1864360b8 Vim motion multi-line yank and delete (#2549)
* Vim motion multi-line yank and delete

* Update changelog

* Cache count in motion mode
2023-08-16 21:18:17 +01:00
Dongdong Zhou c0e77646c5 window tab move actually works 2023-08-15 20:43:49 +01:00
Dongdong Zhou f36c9612ed Move window tab (#2547)
* move window tab draggable

* no border for scm

* drag style
2023-08-14 21:13:37 +01:00
Dongdong Zhou aebbd81000 remote style change 2023-08-13 22:06:06 +01:00
Dongdong Zhou 2454570020 fix buffer save for new file 2023-08-13 21:28:12 +01:00
Dongdong Zhou 1cbd4b9d4a fix active window tab 2023-08-13 17:54:58 +01:00
Dongdong Zhou 069cc72cc9 new way of call floem actions 2023-08-13 17:41:51 +01:00
Jakub Panek 8ef86c8f46 fix: create a backup file before saving instead of creating new file (#2542) 2023-08-13 15:11:56 +01:00
Jakub Panek 4d6a8c1a36 fix: reimplement old way of overriding log targets, but using LAPCE_LOG instaed of RUST_LOG (#2539) 2023-08-13 15:08:48 +01:00
Jakub Panek cb644de14a fix: add proxy status (#2502)
* chore: move status to own file

* feat: move branch status to status bar

* fix: add proxy status

* fix: hide language/text details unless its a tab with file open
2023-08-13 14:52:51 +01:00
Veronika Bušová 6567b41390 Show Lapce as an option when doing "Open With..." on Linux (#2498) 2023-08-13 12:55:02 +02:00
217 changed files with 33959 additions and 28063 deletions
+9
View File
@@ -0,0 +1,9 @@
.github
.vscode
.lapce
docs
extra/linux/docker
extra/macos
extra/windows
target
lapce.spec
+2 -1
View File
@@ -7,5 +7,6 @@ updates:
- package-ecosystem: 'cargo'
directory: '/'
open-pull-requests-limit: 10
schedule:
interval: 'weekly'
interval: 'weekly'
+14 -14
View File
@@ -32,21 +32,20 @@ jobs:
- lapce-app/portable
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install dependencies on Ubuntu
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install clang libxkbcommon-x11-dev pkg-config libvulkan-dev libgtk-3-dev
run: sudo make ubuntu-deps
- name: Update toolchain & add llvm-tools
run: |
rustup update
rustup update --no-self-update
rustup component add llvm-tools-preview
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Fetch dependencies
run: cargo fetch --locked
@@ -58,6 +57,10 @@ jobs:
if: ${{ join(matrix.features, ',') != '' }}
run: cargo build --frozen --features ${{ join(matrix.features, ',') }}
- name: Free space
if: startsWith(matrix.os, 'windows')
run: cargo clean
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
@@ -68,8 +71,7 @@ jobs:
- name: Run doc tests
run: cargo test --doc --workspace
- name: Upload coverage
uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
with:
files: lcov.info
@@ -77,8 +79,7 @@ jobs:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update toolchain & add rustfmt
run: |
@@ -96,17 +97,16 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update toolchain & add clippy
run: |
rustup update
rustup update --no-self-update
rustup component add clippy
- name: Install dependencies on Ubuntu
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install clang libxkbcommon-x11-dev pkg-config libvulkan-dev
run: sudo make ubuntu-deps
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
@@ -115,4 +115,4 @@ jobs:
run: cargo fetch --locked
- name: Run clippy
run: cargo clippy -- -D warnings
run: cargo clippy
+168 -76
View File
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name for release'
description: "Tag name for release"
required: false
default: nightly
push:
@@ -16,10 +16,13 @@ on:
# trigger release workflow only if this file changed
- .github/workflows/release.yml
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_TERM_COLOR: always
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
jobs:
tagname:
@@ -27,30 +30,24 @@ jobs:
outputs:
tag_name: ${{ steps.tag.outputs.tag }}
steps:
- if: github.event_name == 'workflow_dispatch'
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
- id: vars
shell: bash
run: echo "sha_short=${GITHUB_SHA::7}" | tee -a $GITHUB_OUTPUT
- if: github.event_name == 'schedule'
run: echo 'TAG_NAME=nightly' >> $GITHUB_ENV
- if: github.event_name == 'workflow_dispatch'
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" | tee -a $GITHUB_ENV
- if: github.event_name == 'schedule' || github.event_name == 'pull_request'
run: echo 'TAG_NAME=nightly-${{ steps.vars.outputs.sha_short }}' | tee -a $GITHUB_ENV
- if: github.event_name == 'push'
run: |
TAG_NAME=${{ github.ref }}
echo "TAG_NAME=${TAG_NAME#refs/tags/}" >> $GITHUB_ENV
- if: github.event_name == 'pull_request'
run: echo 'TAG_NAME=debug' >> $GITHUB_ENV
- id: vars
shell: bash
run: echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
- if: env.TAG_NAME == 'nightly'
run: echo 'TAG_NAME=nightly-${{ steps.vars.outputs.sha_short }}' >> $GITHUB_ENV
echo "TAG_NAME=${TAG_NAME#refs/tags/}" | tee -a $GITHUB_ENV
- id: tag
run: echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
run: echo "tag=$TAG_NAME" | tee -a $GITHUB_OUTPUT
windows:
runs-on: windows-latest
needs: tagname
@@ -62,25 +59,23 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update rust
run: rustup update
run: rustup update --no-self-update
- name: Fetch dependencies
run: cargo fetch --locked
- name: Build
run: cargo build --frozen --profile release-lto
- name: Install WiX
run: nuget install WiX -Version 3.11.2
- name: Crate msi installer
run: |
./WiX.*/tools/candle.exe -arch "x64" -ext WixUIExtension -ext WixUtilExtension \
candle.exe -arch "x64" -ext WixUIExtension -ext WixUtilExtension \
-out "./lapce.wixobj" "extra/windows/wix/lapce.wxs"
./WiX.*/tools/light.exe -ext WixUIExtension -ext WixUtilExtension \
light.exe -ext WixUIExtension -ext WixUtilExtension \
-out "./Lapce-windows.msi" -sice:ICE61 -sice:ICE91 \
"./lapce.wixobj"
@@ -100,7 +95,7 @@ jobs:
Start-Sleep -Seconds 10
$compressor.close()
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: lapce-windows
path: |
@@ -111,43 +106,113 @@ jobs:
linux:
runs-on: ubuntu-latest
container: ubuntu:18.04
needs: tagname
env:
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
DEBIAN_FRONTEND: noninteractive
steps:
- uses: actions/checkout@v3
- name: Install dependencies
- uses: actions/checkout@v4
- name: Build deb packages
run: |
apt-get -y update
apt-get -y install curl wget clang libxkbcommon-x11-dev pkg-config libvulkan-dev
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-gnu
profile: minimal
- name: Fetch dependencies
run: cargo fetch --locked
- name: Build
run: cargo build --frozen --profile release-lto --bin lapce
docker buildx create --driver=docker-container --use
docker buildx bake --pull ubuntu-focal-binary
- name: Gzip
run: |
mkdir Lapce
cp ./target/release-lto/lapce Lapce/
tar -zcvf ./Lapce-linux.tar.gz Lapce
cp ./target/linux_amd64/lapce Lapce/
tar -zcvf ./lapce-linux-amd64.tar.gz Lapce
- uses: actions/upload-artifact@v3
rm -rf Lapce
mkdir Lapce
cp ./target/linux_arm64/lapce Lapce/
tar -zcvf ./lapce-linux-arm64.tar.gz Lapce
- name: Fetch dependencies
run: cargo fetch --locked
- name: Vendor dependencies
run: |
cargo vendor --frozen > ./vendor-config.toml
mv ./vendor-config.toml ./vendor/
tar -zcf vendor.tar.gz ./vendor/
- uses: actions/upload-artifact@v4
with:
name: lapce-linux
path: |
./Lapce-linux.tar.gz
./lapce-linux-*.tar.gz
./vendor.tar.gz
retention-days: 1
deb:
runs-on: ubuntu-latest
needs: tagname
env:
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
strategy:
fail-fast: false
matrix:
include:
- os-name: debian
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-name: ubuntu
os-version: mantic
# - os-name: ubuntu
# os-version: noble
steps:
- uses: actions/checkout@v4
- 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
with:
name: lapce-${{ matrix.os-name }}-${{ matrix.os-version }}
path: |
./target/linux_*/*
retention-days: 1
rpm:
runs-on: ubuntu-latest
needs: tagname
env:
RELEASE_TAG_NAME: ${{ needs.tagname.outputs.tag_name }}
strategy:
fail-fast: false
matrix:
include:
- os-name: fedora
os-version: 39
- os-name: fedora
os-version: 40
# - os-name: fedora
# os-version: 41
# - os-name: fedora
# os-version: rawhide
steps:
- uses: actions/checkout@v4
- name: Build rpm 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
with:
name: lapce-${{ matrix.os-name }}-${{ matrix.os-version }}
path: |
./target/*
retention-days: 1
linux-musl:
@@ -171,7 +236,7 @@ jobs:
- triple: aarch64-unknown-linux-musl
platform: aarch64
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Alpine Linux for ${{ matrix.platform }} (target arch)
id: alpine-target
@@ -208,11 +273,11 @@ jobs:
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_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
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
run: |
# Workaround for https://github.com/rust-lang/pkg-config-rs/issues/102
@@ -231,7 +296,7 @@ jobs:
run: |
gzip -c ./target/${{ matrix.triple }}/release/lapce-proxy > ./lapce-proxy-linux-${{ matrix.platform }}.gz
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: lapce-proxy-linux-${{ matrix.platform }}
path: |
@@ -248,7 +313,7 @@ jobs:
NOTARIZE_PASSWORD: ${{ secrets.NOTARIZE_PASSWORD }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install ARM target
run: rustup update && rustup target add aarch64-apple-darwin
@@ -274,16 +339,22 @@ jobs:
gzip -c ./target/x86_64-apple-darwin/release-lto/lapce-proxy > ./target/release-lto/macos/lapce-proxy-darwin-x86_64.gz
gzip -c ./target/aarch64-apple-darwin/release-lto/lapce-proxy > ./target/release-lto/macos/lapce-proxy-darwin-aarch64.gz
- name: "Notarize Release Build"
run: |
npx notarize-cli --file ./target/release-lto/macos/Lapce-macos.dmg --bundle-id io.lapce --asc-provider CYSGAZFR8D
- name: Notarize Release Build
uses: lando/notarize-action@v2
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"
- name: "Staple Release Build"
uses: lapce/xcode-staple@062485d6eeafe841c18a412f012e80f49e23c517
with:
product-path: "./target/release-lto/macos/Lapce-macos.dmg"
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: lapce-macos
path: |
@@ -292,8 +363,13 @@ jobs:
retention-days: 1
publish:
if: github.event_name != 'pull_request'
needs: [linux, linux-musl, windows, macos]
needs:
- linux
- linux-musl
- deb
- rpm
- windows
- macos
runs-on: ubuntu-latest
env:
GH_REPO: ${{ github.repository }}
@@ -303,35 +379,51 @@ jobs:
steps:
# Must perform checkout first, since it deletes the target directory
# before running, and would therefore delete the downloaded artifacts
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
- if: github.event_name == 'workflow_dispatch'
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV
run: echo "TAG_NAME=${{ github.event.inputs.tag_name }}" | tee -a $GITHUB_ENV
- if: github.event_name == 'schedule'
run: echo 'TAG_NAME=nightly' >> $GITHUB_ENV
run: echo 'TAG_NAME=nightly' | tee -a $GITHUB_ENV
- if: github.event_name == 'push'
run: |
TAG_NAME=${{ github.ref }}
echo "TAG_NAME=${TAG_NAME#refs/tags/}" >> $GITHUB_ENV
echo "TAG_NAME=${TAG_NAME#refs/tags/}" | tee -a $GITHUB_ENV
- if: env.TAG_NAME == 'nightly'
run: |
(echo 'SUBJECT=Lapce development build';
echo 'PRERELEASE=--prerelease') >> $GITHUB_ENV
{
echo 'SUBJECT=Lapce development build'
echo 'PRERELEASE=--prerelease'
} | tee -a $GITHUB_ENV
- if: env.TAG_NAME == 'nightly' && github.event_name != 'pull_request'
name: Re-Tag nightly
run: |
gh release delete nightly --yes || true
git push origin :nightly || true
- if: env.TAG_NAME != 'nightly'
run: |
(echo 'SUBJECT=Lapce release build';
echo 'PRERELEASE=') >> $GITHUB_ENV
{
echo 'SUBJECT=Lapce release build'
echo 'PRERELEASE='
} | tee -a $GITHUB_ENV
- name: Publish release
if: github.event_name != 'pull_request'
env:
DEBUG: api
run: |
gh release create $TAG_NAME $PRERELEASE --title "$TAG_NAME" --target $GITHUB_SHA lapce-macos/* lapce-linux/* lapce-proxy-linux-*/* lapce-windows/*
gh release create $TAG_NAME $PRERELEASE --title "$TAG_NAME" --target $GITHUB_SHA \
lapce-macos/* \
lapce-linux/* \
lapce-debian*/*/* \
lapce-ubuntu*/*/* \
lapce-fedora*/* \
lapce-proxy-linux-*/* \
lapce-windows/*
+2 -1
View File
@@ -6,6 +6,7 @@ shell.nix
lib/
target/
core/languages/
vendor/
.DS_Store
*.patch
*.patch
+2 -2
View File
@@ -1,4 +1,4 @@
max_width = 85
# below requires nightly
# imports_granularity = "Crate"
# group_imports = "StdExternalCrate"
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
+13
View File
@@ -0,0 +1,13 @@
include = ["**/*.toml"]
exclude = ["lapce-proxy/src/**"]
[[rule]]
include = ["**/Cargo.toml", ".taplo.toml", "Cargo.toml"]
[rule.formatting]
align_entries = true
align_comments = true
array_trailing_comma = true
trailing_newline = true
column_width = 100
inline_table_expand = false
+48 -1
View File
@@ -3,9 +3,56 @@
## Unreleased
### Features/Changes
- [#2425](https://github.com/lapce/lapce/pull/2425): Reimplement completion lens
### Bug Fixes
- [#3203](https://github.com/lapce/lapce/pull/3203): Fallback to default theme is theme key is not found
## 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
- [#2918](https://github.com/lapce/lapce/pull/2918): Allow searching the shortcuts overview by shortcut (e.g. "Ctrl+P") or when condition (e.g. "list_focus")
- [#2955](https://github.com/lapce/lapce/pull/2955): Using glob patterns to hide files or directories in explorer
- [#3026](https://github.com/lapce/lapce/pull/3026): Add missing file explorer context menu entries
- [#3047](https://github.com/lapce/lapce/pull/3047): Add support for different CrLf/Lf line endings per-file
- [#3053](https://github.com/lapce/lapce/pull/3053): Add tooltips to various places
- [#3069](https://github.com/lapce/lapce/pull/3069): Allow color variables for themes
- [#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
- [#2549](https://github.com/lapce/lapce/pull/2549): Implement multi-line vim-motion yank and delete (`3dd`, `2yy`, etc.)
- [#2553](https://github.com/lapce/lapce/pull/2553): Implement search and replace
- [#1809](https://github.com/lapce/lapce/pull/1809): Implement debug adapter protocol
### Bug Fixes
- [#2650](https://github.com/lapce/lapce/pull/2650): Inform language servers that Lapce supports LSP diagnostics
## 0.2.8
+22 -6
View File
@@ -1,22 +1,38 @@
# How to contribute
Thank you for your interest in contributing to Lapce! No contribution is too small and we consider _all_ contributions to the project. There are many ways to contribute (a few are listed here) but if you think of something else, join us on [Discord](https://discord.gg/n8tGJ6Rn6D) or let us know via an [issue](https://github.com/lapce/lapce/issues).
A summary of how to contribute is below. If you are a _user_, also check out [this guide](docs/new-contributor-guide-user.md). If you are a _developer_, also check out [this guide](docs/new-contributor-guide-developer.md).
## Questions
We're always around hanging on our [Discord](https://discord.gg/n8tGJ6Rn6D) server but if you're only participating on GitHub, you can open a [Discussion](https://github.com/lapce/lapce/discussions)
## Feature Requests
A feature request is _something you want to have included in Lapce_. We track feature requests on GitHub via [issues](https://github.com/lapce/lapce/issues). There are generally two kinds of features:
A feature request is _editor behaviour that you want to have included in Lapce_. We track feature requests on GitHub via [issues](https://github.com/lapce/lapce/issues). There are generally few kinds of features:
1. A feature more suited to the core development of Lapce. If this is the case please make a suggestion in an [issue](https://github.com/lapce/lapce/issues).
2. A feature that is more suited to a Plugin. We currently have an issue to track requests specifically for Plugins! You can view the discussion and make suggestions [here](https://github.com/lapce/lapce/issues/558).
### Core features
To reduce the number of duplicate requests, please search through the issues to see if something has already been suggested. If a feature you want has been suggested, then comment on that issue to let us know it's popular. You can use emoji-reactions to show us just how popular!
A feature more suited to the core development of Lapce. If this is the case please make a suggestion in an [issue](https://github.com/lapce/lapce/issues).
### Programming language support (autocompletion/intellisense/formatting)
A feature that relates to specific programming language or development tool that provides intellisense, or various editor commands.
We do not track plugins development here, each plugin should have own issue tracker with eventual issues linked/referenced to main Lapce issue tracker.
### Syntax highlighting
There is main issue for tracking syntax highlighting grammars support at https://github.com/lapce/lapce/issues/272.
---
To reduce the number of duplicate requests, please search through the issues to see if something has already been suggested. If a feature you want has been suggested, then comment on that issue to let us know it's popular. You can use emoji-reactions to show us just how popular.
## Bug Reports
Bugs should also be reported on GitHub via [issues](https://github.com/lapce/lapce/issues). This allows us to track them and see how prevalent they are.
If you encounter a bug when using Lapce, check the issues to see if anyone else has encountered it. If it already exists, you can use emoji reactions to indicate it should be fixed ASAP!
If you encounter a bug when using Lapce, check the issues to see if anyone else has encountered it. If it already exists, you can use emoji reactions so we can see community interest in specific issues and how important they are.
Please follow the rule of [NoPlusOne](https://github.com/golang/go/wiki/NoPlusOne)
## Pull Requests
Generated
+2956 -1988
View File
File diff suppressed because it is too large Load Diff
+78 -58
View File
@@ -1,13 +1,14 @@
[package]
name = "lapce"
version = "0.2.8"
authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
edition = "2021"
rust-version = "1.65"
default-run = "lapce"
name = "lapce"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
rust-version = { workspace = true }
default-run = "lapce"
[dependencies]
lapce-app = { path = "./lapce-app" }
lapce-app = { path = "./lapce-app" }
lapce-proxy = { path = "./lapce-proxy" }
[[bin]]
@@ -19,79 +20,97 @@ name = "lapce-proxy"
path = "lapce-proxy/src/bin/lapce-proxy.rs"
[workspace]
members = [
"lapce-app",
"lapce-proxy",
"lapce-rpc",
"lapce-core",
]
members = ["lapce-app", "lapce-proxy", "lapce-rpc", "lapce-core"]
[workspace.package]
version = "0.2.8"
edition = "2021"
rust-version = "1.64"
homepage = "https://lapce.dev"
authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
version = "0.4.0"
edition = "2021"
rust-version = "1.75"
license = "Apache-2.0"
homepage = "https://lapce.dev"
authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
[workspace.dependencies]
anyhow = "1.0"
chrono = "0.4"
clap = { version = "3.2.17", features = ["derive"] }
crossbeam-channel = "0.5.0"
directories = "4.0.1"
flate2 = "1.0"
hashbrown = { version = "0.12.3", features = ["serde"] }
im = { version = "15.0.0", features = ["serde"] }
include_dir = "0.6.2"
indexmap = { version = "1.9", features = ["serde"] }
interprocess = "1.1.1"
itertools = "0.10.1"
notify = { version = "5.0.0", features = ["serde"] }
once_cell = "1.17"
parking_lot = { version = "0.11.0", features = ["deadlock_detection"] }
rayon = "1.5.1"
regex = "1.7.0"
reqwest = { version = "0.11", features = ["blocking", "json", "socks"] }
serde = "1.0"
serde_json = "1.0"
smallvec = "1.10.0"
strum = "0.21.0" # follow same version as system-deps in lockfile
strum_macros = "0.21.1" # ditto
tar = "0.4"
thiserror = "1.0"
toml_edit = { version = "0.14.4", features = ["easy"] }
anyhow = { version = "1.0" }
backtrace = { version = "0.3" }
chrono = { version = "0.4" }
clap = { version = "4.5.0", default-features = false, features = ["std", "help", "usage", "derive"] }
crossbeam-channel = { version = "0.5.12" }
directories = { version = "4.0.1" }
flate2 = { version = "1.0" }
globset = { version = "0.4.14" }
hashbrown = { version = "0.14.5", features = ["serde"] }
im = { version = "15.0.0", features = ["serde"] }
include_dir = { version = "0.6.2" }
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.3" }
rayon = { version = "1.10.0" }
regex = { version = "1.10.4" }
reqwest = { version = "0.11", features = ["blocking", "json", "socks"] }
serde = { version = "1.0" }
serde_json = { version = "1.0" }
smallvec = { version = "1.13.2" }
strum = { version = "0.26.2" }
strum_macros = { version = "0.26.2" }
tar = { version = "0.4" }
thiserror = { version = "1.0" }
toml = { version = "*" }
toml_edit = { version = "0.20.2", features = ["serde"] }
url = { version = "2.5.0" }
lsp-types = { version = "0.93", features = ["proposed"] }
psp-types = { git = "https://github.com/lapce/psp-types" }
dap-types = { git = "https://github.com/lapce/dap-types", rev = "913a2a520416a2a81a1f09a7946f595d5ae3ee4a", package = "dap" }
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" }
lapce-xi-rope = { version = "0.3.2", features = ["serde"] }
lapce-core = { path = "./lapce-core" }
lapce-rpc = { path = "./lapce-rpc" }
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 = "c9e3d6a131c63286dc634b27c38b95bde43ed90d", features = ["serde"] }
# floem-editor-core = { path = "../workspaces/floem/editor-core/", features = ["serde"] }
[patch.crates-io]
# Temporarily patch lsp-types with a version that supports inline-completion
lsp-types = { git = "https://github.com/lapce/lsp-types", rev = "feaa1e2ec80975c9dadd400a238ceacf071058e6" }
regalloc2 = { rev = "5d79e12d0a93b10fc181f4da409b4671dd365228", git = "https://github.com/bytecodealliance/regalloc2" }
[workspace.dependencies.tracing]
git = "https://github.com/tokio-rs/tracing"
rev = "c14525e1610db88986f849d46bd3e9795878b012"
git = "https://github.com/tokio-rs/tracing"
rev = "908cc432a5994f6e17c8f36e13c217dc40085704"
package = "tracing"
[workspace.dependencies.tracing-log]
git = "https://github.com/tokio-rs/tracing"
rev = "908cc432a5994f6e17c8f36e13c217dc40085704"
package = "tracing-log"
[workspace.dependencies.tracing-subscriber]
git = "https://github.com/tokio-rs/tracing"
rev = "c14525e1610db88986f849d46bd3e9795878b012"
git = "https://github.com/tokio-rs/tracing"
rev = "908cc432a5994f6e17c8f36e13c217dc40085704"
package = "tracing-subscriber"
[workspace.dependencies.tracing-appender]
git = "https://github.com/tokio-rs/tracing"
rev = "c14525e1610db88986f849d46bd3e9795878b012"
git = "https://github.com/tokio-rs/tracing"
rev = "908cc432a5994f6e17c8f36e13c217dc40085704"
package = "tracing-appender"
[workspace.dependencies.alacritty_terminal]
git = "https://github.com/lapce/alacritty"
branch = "release/v0.12.1/lapce"
version = "0.23.0"
[workspace.dependencies.windows-sys]
version = "0"
features = ["Win32_Foundation", "Win32_System_Threading"]
[profile.release-lto]
inherits = "release"
lto = true
inherits = "release"
lto = true
codegen-units = 1
# A profile which compiles all (non-workspace) dependencies in release mode
@@ -107,3 +126,4 @@ opt-level = 3
[profile.fastdev]
inherits = "dev"
+4
View File
@@ -24,6 +24,10 @@ 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}'
ubuntu-deps:
apt-get update -y
apt-get install -y clang libxkbcommon-x11-dev pkg-config libvulkan-dev libgtk-3-dev libwayland-dev xorg-dev libxcb-shape0-dev libxcb-xfixes0-dev
binary: $(TARGET)-native ## Build a release binary
binary-universal: $(TARGET)-universal ## Build a universal release binary
$(TARGET)-native:
+5 -5
View File
@@ -14,17 +14,17 @@
<a href="https://discord.gg/n8tGJ6Rn6D" target="_blank">
<img src="https://img.shields.io/discord/946858761413328946?logo=discord" />
</a>
<a href="https://matrix.to/#/#lapce-editor:matrix.org" target="_blank">
<img src="https://img.shields.io/matrix/lapce-editor:matrix.org?color=turquoise&logo=Matrix" />
</a>
<a href="https://docs.lapce.dev" target="_blank">
<img src="https://img.shields.io/static/v1?label=Docs&message=docs.lapce.dev&color=blue" alt="Lapce Docs">
</a>
<a href="https://wiki.mutable.ai/lapce/lapce" target="_blank">
<img src="https://img.shields.io/static/v1?label=Code Wiki&message=Mutable.ai&color=blue" alt="Mutable.ai Auto Wiki">
</a>
</div>
<br/>
Lapce (IPA: /læps/) is written in pure Rust with a UI in [Floem](https://github.com/lapce/floem) (which is also written in Rust). 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) 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/).
![](https://github.com/lapce/lapce/blob/master/extra/images/screenshot.png?raw=true)
@@ -32,7 +32,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://github.com/lapce/lapdev) 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.
+57 -39
View File
@@ -26,8 +26,15 @@ red = "#E06C75"
white = "#ABB2BF"
yellow = "#E5C07B"
primary-background = "$black"
# Background for 'secondary' elements: panels, palette, status bar, completion, hover
secondary-background = "#21252B"
current-background = "#2C313A"
text = "$white"
dim-text = "#5C6370"
[color-theme.syntax]
"comment" = "#5C6370"
"comment" = "$dim-text"
"constant" = "$yellow"
"type" = "$yellow"
"typeAlias" = "$yellow"
@@ -58,9 +65,14 @@ yellow = "#E5C07B"
"text.uri" = "$cyan"
"text.reference" = "$yellow"
"variable" = "$red"
"variable.other.member" = "$green"
"variable.other.member" = "$red"
"tag" = "$blue"
"bracket.color.1" = "$blue"
"bracket.color.2" = "$yellow"
"bracket.color.3" = "$purple"
"bracket.unpaired" = "$red"
[color-theme.ui]
"lapce.error" = "$red"
"lapce.warn" = "$yellow"
@@ -72,18 +84,18 @@ yellow = "#E5C07B"
"lapce.button.primary.foreground" = "$black"
# tab
"lapce.tab.active.background" = "$black"
"lapce.tab.active.foreground" = "$white"
"lapce.tab.active.background" = "$primary-background"
"lapce.tab.active.foreground" = "$text"
"lapce.tab.active.underline" = "#528BFF"
"lapce.tab.inactive.background" = "#21252b"
"lapce.tab.inactive.foreground" = "$white"
"lapce.tab.inactive.background" = "$secondary-background"
"lapce.tab.inactive.foreground" = "$text"
"lapce.tab.inactive.underline" = "#528BFF77"
"lapce.tab.separator" = ""
"lapce.icon.active" = "$white"
"lapce.icon.inactive" = "#5C6370"
"lapce.icon.active" = "$text"
"lapce.icon.inactive" = "$dim-text"
"lapce.remote.icon" = "$black"
"lapce.remote.local" = "#4078F2"
@@ -92,66 +104,72 @@ yellow = "#E5C07B"
"lapce.remote.disconnected" = "#E45649"
"lapce.plugin.name" = "#DDDDDD"
"lapce.plugin.description" = "$white"
"lapce.plugin.description" = "$text"
"lapce.plugin.author" = "#B0B0B0"
"editor.background" = "$black"
"editor.foreground" = "$white"
"editor.dim" = "#5C6370"
"editor.background" = "$primary-background"
"editor.foreground" = "$text"
"editor.dim" = "$dim-text"
"editor.focus" = "#CCCCCC"
"editor.caret" = "#528BFF"
"editor.selection" = "$grey"
"editor.current_line" = "#2C313C"
"editor.debug_break_line" = "#528abF37"
"editor.link" = "$blue"
"editor.visible_whitespace" = "$grey"
"editor.indent_guide" = "$grey"
"editor.drag_drop_background" = "#79c1fc55"
"editor.drag_drop_tab_background" = "#0b0e1455"
"editor.sticky_header_background" = "$black"
"editor.sticky_header_background" = "$primary-background"
"inlay_hint.foreground" = "$white"
"inlay_hint.foreground" = "$text"
"inlay_hint.background" = "#528abF37"
"error_lens.error.foreground" = "$red"
"error_lens.error.background" = "#E06C7520"
"error_lens.warning.foreground" = "$yellow"
"error_lens.warning.background" = "#E5C07B20"
"error_lens.other.foreground" = "#5C6370"
"error_lens.other.foreground" = "$dim-text"
"error_lens.other.background" = "#5C637020"
"completion_lens.foreground" = "$dim-text"
"source_control.added" = "#50A14FCC"
"source_control.removed" = "#FF5266CC"
"source_control.modified" = "#0184BCCC"
"palette.background" = "#21252B"
"palette.foreground" = "$white"
"palette.current.background" = "#2C313A"
"palette.current.foreground" = "$white"
"tooltip.background" = "$primary-background"
"tooltip.foreground" = "$text"
"completion.background" = "#21252B"
"completion.current" = "#2C313A"
"palette.background" = "$secondary-background"
"palette.foreground" = "$text"
"palette.current.background" = "$current-background"
"palette.current.foreground" = "$text"
"hover.background" = "#21252B"
"completion.background" = "$secondary-background"
"completion.current" = "$current-background"
"activity.background" = "#21252B"
"activity.current" = "$black"
"hover.background" = "$secondary-background"
"activity.background" = "$secondary-background"
"activity.current" = "$primary-background"
"debug.breakpoint" = "$red"
"debug.breakpoint.hover" = "$grey"
"debug.breakpoint.hover" = "#E06C7566"
"panel.background" = "#21252B"
"panel.foreground" = "$white"
"panel.foreground.dim" = "#5C6370"
"panel.current.background" = "#2C313A"
"panel.current.foreground" = "$white"
"panel.current.foreground.dim" = "#5C6370"
"panel.background" = "$secondary-background"
"panel.foreground" = "$text"
"panel.foreground.dim" = "$dim-text"
"panel.current.background" = "$current-background"
"panel.current.foreground" = "$text"
"panel.current.foreground.dim" = "$dim-text"
"panel.hovered.background" = "#343A45"
"panel.hovered.active.background" = "#5C6370"
"panel.hovered.foreground" = "$white"
"panel.hovered.foreground.dim" = "#5C6370"
"panel.hovered.active.background" = "$dim-text"
"panel.hovered.foreground" = "$text"
"panel.hovered.foreground.dim" = "$dim-text"
"status.background" = "#21252B"
"status.foreground" = "$white"
"status.background" = "$secondary-background"
"status.foreground" = "$text"
"status.modal.normal.background" = "$blue"
"status.modal.normal.foreground" = "$black"
"status.modal.insert.background" = "$red"
@@ -163,9 +181,9 @@ yellow = "#E5C07B"
"markdown.blockquote" = "#898989"
"terminal.cursor" = "$white"
"terminal.foreground" = "$white"
"terminal.background" = "$black"
"terminal.cursor" = "$text"
"terminal.foreground" = "$text"
"terminal.background" = "$primary-background"
"terminal.white" = "$white"
"terminal.black" = "$black"
"terminal.red" = "$red"
+137
View File
@@ -4,6 +4,143 @@
name = "Lapce Codicons"
[icon-theme.ui]
"logo" = "lapce_logo.svg"
"menu" = "menu.svg"
"link" = "link.svg"
"error" = "error.svg"
"add" = "add.svg"
"close" = "close.svg"
"remote" = "remote.svg"
"unsaved" = "circle-filled.svg"
"warning" = "warning.svg"
"problem" = "problem.svg"
"debug" = "debug.svg"
"debug_breakpoint" = "circle-filled.svg"
"debug_alt" = "debug-alt.svg"
"debug_small" = "debug-alt-small.svg"
"debug_restart" = "debug-restart.svg"
"debug_continue" = "debug-continue.svg"
"debug_step_over" = "debug-step-over.svg"
"debug_step_into" = "debug-step-into.svg"
"debug_step_out" = "debug-step-out.svg"
"debug_pause" = "debug-pause.svg"
"debug_stop" = "debug-stop.svg"
"debug_console" = "debug-console.svg"
"debug_disconnect" = "debug-disconnect.svg"
"start" = "debug-start.svg"
"run_errors" = "run-errors.svg"
"settings" = "settings-gear.svg"
"terminal" = "terminal.svg"
"lightbulb" = "lightbulb.svg"
"extensions" = "extensions.svg"
"keyboard" = "keyboard.svg"
"breadcrumb_separator" = "chevron-right.svg"
"symbol_color" = "symbol-color.svg"
"window.close" = "chrome-close.svg"
"window.restore" = "chrome-restore.svg"
"window.maximize" = "chrome-maximize.svg"
"window.minimize" = "chrome-minimize.svg"
"file" = "file.svg"
"file_explorer" = "files.svg"
"file_picker_up" = "arrow-up.svg"
"image_loading" = "refresh.svg"
"image_error" = "error.svg"
"scm.icon" = "source-control.svg"
"scm.diff.modified" = "diff-modified.svg"
"scm.diff.added" = "diff-added.svg"
"scm.diff.removed" = "diff-removed.svg"
"scm.diff.renamed" = "diff-renamed.svg"
"scm.change.add" = "add.svg"
"scm.change.remove" = "remove.svg"
"palette.menu" = "chevron-down.svg"
"fold" = "fold.svg"
"fold.up" = "fold-up.svg"
"fold.down" = "fold-down.svg"
"dropdown.arrow" = "chevron-down.svg"
"panel.fold-down" = "chevron-down.svg"
"panel.fold-up" = "chevron-right.svg"
"location.forward" = "arrow-right.svg"
"location.backward" = "arrow-left.svg"
"item.opened" = "chevron-down.svg"
"item.closed" = "chevron-right.svg"
"directory.closed" = "folder.svg"
"directory.opened" = "folder-opened.svg"
"panel.restore" = "chevron-down.svg"
"panel.maximise" = "chevron-right.svg"
"split.horizontal" = "split-horizontal.svg"
"tab.previous" = "chevron-left.svg"
"tab.next" = "chevron-right.svg"
"sidebar.left.on" = "layout-sidebar-left.svg"
"sidebar.left.off" = "layout-sidebar-left-off.svg"
"sidebar.right.on" = "layout-sidebar-right.svg"
"sidebar.right.off" = "layout-sidebar-right-off.svg"
"layout.panel.on" = "layout-panel.svg"
"layout.panel.off" = "layout-panel-off.svg"
"search.icon" = "search.svg"
"search.clear" = "close.svg"
"search.forward" = "arrow-down.svg"
"search.backward" = "arrow-up.svg"
"search.case_sensitive" = "case-sensitive.svg"
"search.whole_word" = "whole-word.svg"
"search.regex" = "regex.svg"
"search.replace" = "replace.svg"
"search.replace_all" = "replace-all.svg"
"symbol_kind.array" = "symbol-array.svg"
"symbol_kind.boolean" = "symbol-boolean.svg"
"symbol_kind.class" = "symbol-class.svg"
"symbol_kind.constant" = "symbol-constant.svg"
"symbol_kind.enum_member" = "symbol-enum-member.svg"
"symbol_kind.enum" = "symbol-enum.svg"
"symbol_kind.event" = "symbol-event.svg"
"symbol_kind.field" = "symbol-field.svg"
"symbol_kind.file" = "symbol-file.svg"
"symbol_kind.function" = "symbol-method.svg"
"symbol_kind.interface" = "symbol-interface.svg"
"symbol_kind.key" = "symbol-key.svg"
"symbol_kind.method" = "symbol-method.svg"
"symbol_kind.namespace" = "symbol-namespace.svg"
"symbol_kind.number" = "symbol-numeric.svg"
"symbol_kind.object" = "symbol-namespace.svg"
"symbol_kind.operator" = "symbol-operator.svg"
"symbol_kind.property" = "symbol-property.svg"
"symbol_kind.string" = "symbol-string.svg"
"symbol_kind.struct" = "symbol-structure.svg"
"symbol_kind.type_parameter" = "symbol-parameter.svg"
"symbol_kind.variable" = "symbol-variable.svg"
"completion_item_kind.class" = "symbol-class.svg"
"completion_item_kind.constant" = "symbol-constant.svg"
"completion_item_kind.enum_member" = "symbol-enum-member.svg"
"completion_item_kind.enum" = "symbol-enum.svg"
"completion_item_kind.field" = "symbol-field.svg"
"completion_item_kind.function" = "symbol-method.svg"
"completion_item_kind.interface" = "symbol-interface.svg"
"completion_item_kind.keyword" = "symbol-keyword.svg"
"completion_item_kind.method" = "symbol-method.svg"
"completion_item_kind.module" = "symbol-namespace.svg"
"completion_item_kind.property" = "symbol-property.svg"
"completion_item_kind.snippet" = "symbol-snippet.svg"
"completion_item_kind.string" = "symbol-string.svg"
"completion_item_kind.struct" = "symbol-structure.svg"
"completion_item_kind.variable" = "symbol-variable.svg"
[icon-theme.foldername]
+42 -22
View File
@@ -32,7 +32,7 @@ mode = "i"
[[keymaps]]
key = "shift+backspace"
command = "delete_backward"
command = "delete_forward"
mode = "i"
[[keymaps]]
@@ -109,7 +109,7 @@ command = "jump_location_backward"
mode = "i"
[[keymaps]]
key = "ctrl+_"
key = "ctrl+shift+-"
command = "jump_location_forward"
mode = "i"
@@ -205,16 +205,36 @@ mode = "n"
[[keymaps]]
key = "esc"
command = "modal.close"
when = "modal_focus"
when = "modal_focus || completion_focus"
[[keymaps]]
key = "ctrl+b"
command = "left"
key = "tab"
command = "inline_completion.select"
when = "inline_completion_visible && !search_focus && !modal_focus && !list_focus && !search_active"
mode = "i"
[[keymaps]]
key = "ctrl+f"
command = "right"
key = "esc"
command = "inline_completion.cancel"
when = "inline_completion_visible && !search_focus && !modal_focus && !list_focus && !search_active"
mode = "i"
[[keymaps]]
key = "alt+["
command = "inline_completion.previous"
when = "inline_completion_visible && !search_focus && !modal_focus && !list_focus && !search_active"
mode = "i"
[[keymaps]]
key = "alt+]"
command = "inline_completion.next"
when = "inline_completion_visible && !search_focus && !modal_focus && !list_focus && !search_active"
mode = "i"
[[keymaps]]
key = "alt+\\"
command = "inline_completion.invoke"
when = "!inline_completion_visible && !search_focus && !modal_focus && !list_focus && !search_active"
mode = "i"
[[keymaps]]
@@ -287,7 +307,7 @@ mode = "i"
[[keymaps]]
key = "tab"
command = "insert_tab"
when = "!in_snippet && !completion_focus && !search_focus && !replace_focus"
when = "!in_snippet && !completion_focus && !inline_completion_visible && !search_focus && !replace_focus"
mode = "i"
[[keymaps]]
@@ -312,7 +332,7 @@ mode = "i"
key = "esc"
command = "normal_mode"
mode = "niv"
when = "!search_focus && !modal_focus && !search_active"
when = "!search_focus && !modal_focus && !search_active && !inline_completion_visible"
[[keymaps]]
key = "ctrl+c"
@@ -327,7 +347,7 @@ mode = "niv"
when = "!search_focus && !search_active"
[[keymaps]]
key = ":"
key = "shift+;"
command = "palette.command"
mode = "n"
@@ -487,12 +507,12 @@ command = "line_start"
mode = "nv"
[[keymaps]]
key = "^"
key = "shift+6"
command = "line_start_non_blank"
mode = "nv"
[[keymaps]]
key = "$"
key = "shift+4"
command = "line_end"
mode = "nv"
@@ -587,12 +607,12 @@ command = "motion_mode_delete"
mode = "n"
[[keymaps]]
key = ">"
key = "shift+."
command = "motion_mode_indent"
mode = "n"
[[keymaps]]
key = "<"
key = "shift+,"
command = "motion_mode_outdent"
mode = "n"
@@ -602,7 +622,7 @@ command = "motion_mode_yank"
mode = "n"
[[keymaps]]
key = "*"
key = "shift+8"
command = "search_whole_word_forward"
mode = "nv"
@@ -617,27 +637,27 @@ command = "search_backward"
mode = "nv"
[[keymaps]]
key = "%"
key = "shift+5"
command = "match_pairs"
mode = "nv"
[[keymaps]]
key = "] )"
key = "] shift+0"
command = "next_unmatched_right_bracket"
mode = "nv"
[[keymaps]]
key = "[ ("
key = "[ shift+9"
command = "previous_unmatched_left_bracket"
mode = "nv"
[[keymaps]]
key = "] }"
key = "] shift+]"
command = "next_unmatched_right_curly_bracket"
mode = "nv"
[[keymaps]]
key = "[ {"
key = "[ shift+["
command = "previous_unmatched_left_curly_bracket"
mode = "nv"
@@ -702,12 +722,12 @@ command = "toggle_code_lens"
mode = "nv"
[[keymaps]]
key = ">"
key = "shift+."
command = "indent_line"
mode = "v"
[[keymaps]]
key = "<"
key = "shift+,"
command = "outdent_line"
mode = "v"
+18 -1
View File
@@ -130,7 +130,7 @@ command = "delete_word_forward"
mode = "i"
[[keymaps]]
key = "meta+|"
key = "meta+shift+\\"
command = "match_pairs"
mode = "i"
@@ -275,6 +275,23 @@ command = "document_start"
key = "meta+down"
command = "document_end"
[[keymaps]]
key = "ctrl+f"
command = "right"
mode = "i"
[[keymaps]]
key = "ctrl+p"
command = "up"
when = "!list_focus"
mode = "i"
[[keymaps]]
key = "ctrl+n"
command = "down"
when = "!list_focus"
mode = "i"
[[keymaps]]
key = "meta+shift+o"
command = "palette.symbol"
+23 -1
View File
@@ -125,7 +125,7 @@ command = "delete_word_forward"
mode = "i"
[[keymaps]]
key = "ctrl+|"
key = "ctrl+shift+\\"
command = "match_pairs"
mode = "i"
@@ -275,6 +275,28 @@ command = "document_start"
key = "Ctrl+End"
command = "document_end"
[[keymaps]]
key = "ctrl+b"
command = "left"
mode = "n"
[[keymaps]]
key = "ctrl+f"
command = "right"
mode = "n"
[[keymaps]]
key = "ctrl+p"
command = "up"
when = "!list_focus"
mode = "n"
[[keymaps]]
key = "ctrl+n"
command = "down"
when = "!list_focus"
mode = "n"
[[keymaps]]
key = "ctrl+shift+o"
command = "palette.symbol"
+56 -38
View File
@@ -26,8 +26,15 @@ red = "#E45649"
white = "#FAFAFA"
yellow = "#C18401"
primary-background = "$white"
# Background for 'secondary' elements: panels, palette, status bar, completion, hover
secondary-background = "#EAEAEB"
current-background = "#DBDBDC"
text = "$black"
dim-text = "#A0A1A7"
[color-theme.syntax]
"comment" = "#A0A1A7"
"comment" = "$dim-text"
"constant" = "$yellow"
"type" = "$yellow"
"typeAlias" = "$yellow"
@@ -64,9 +71,14 @@ yellow = "#C18401"
"text.uri" = "$cyan"
"text.reference" = "$yellow"
"variable" = "$red"
"variable.other.member" = "$green"
"variable.other.member" = "$red"
"tag" = "$blue"
"bracket.color.1" = "$blue"
"bracket.color.2" = "$yellow"
"bracket.color.3" = "$purple"
"bracket.unpaired" = "$red"
[color-theme.ui]
"lapce.error" = "#E51400"
"lapce.warn" = "#E9A700"
@@ -78,18 +90,18 @@ yellow = "#C18401"
"lapce.button.primary.foreground" = "$white"
# tab
"lapce.tab.active.background" = "$white"
"lapce.tab.active.foreground" = "$black"
"lapce.tab.active.background" = "$primary-background"
"lapce.tab.active.foreground" = "$text"
"lapce.tab.active.underline" = "#528BFF"
"lapce.tab.inactive.background" = "#EAEAEB"
"lapce.tab.inactive.foreground" = "$black"
"lapce.tab.inactive.foreground" = "$text"
"lapce.tab.inactive.underline" = "#528BFF77"
"lapce.tab.separator" = "#B4B4B4"
"lapce.icon.active" = "$black"
"lapce.icon.inactive" = "#A0A1A7"
"lapce.icon.active" = "$text"
"lapce.icon.inactive" = "$dim-text"
"lapce.remote.icon" = "$white"
"lapce.remote.local" = "#4078F2"
@@ -98,12 +110,12 @@ yellow = "#C18401"
"lapce.remote.disconnected" = "#E45649"
"lapce.plugin.name" = "#444444"
"lapce.plugin.description" = "$black"
"lapce.plugin.description" = "$text"
"lapce.plugin.author" = "#707070"
"terminal.cursor" = "$black"
"terminal.foreground" = "$black"
"terminal.background" = "$white"
"terminal.cursor" = "$text"
"terminal.foreground" = "$text"
"terminal.background" = "$primary-background"
"terminal.white" = "$white"
"terminal.black" = "$black"
"terminal.red" = "$red"
@@ -121,63 +133,69 @@ yellow = "#C18401"
"terminal.bright_magenta" = "$magenta"
"terminal.bright_black" = "#A0A1A7"
"editor.background" = "$white"
"editor.foreground" = "$black"
"editor.dim" = "#A0A1A7"
"editor.background" = "$primary-background"
"editor.foreground" = "$text"
"editor.dim" = "$dim-text"
"editor.focus" = "#000000"
"editor.caret" = "#526FFF"
"editor.selection" = "$grey"
"editor.current_line" = "#F2F2F2"
"editor.debug_break_line" = "#528bFF55"
"editor.link" = "$blue"
"editor.visible_whitespace" = "$grey"
"editor.indent_guide" = "$grey"
"editor.drag_drop_background" = "#79c1fc33"
"editor.drag_drop_tab_background" = "#0b0e1433"
"editor.sticky_header_background" = "$white"
"editor.sticky_header_background" = "$primary-background"
"inlay_hint.foreground" = "$black"
"inlay_hint.foreground" = "$text"
"inlay_hint.background" = "#528bFF55"
"error_lens.error.foreground" = "$red"
"error_lens.error.background" = "#E4564920"
"error_lens.warning.foreground" = "$yellow"
"error_lens.warning.background" = "#C1840120"
"error_lens.other.foreground" = "#A0A1A7"
"error_lens.other.foreground" = "$dim-text"
"error_lens.other.background" = "#A0A1A720"
"completion_lens.foreground" = "$dim-text"
"source_control.added" = "#50A14FCC"
"source_control.removed" = "#FF5266CC"
"source_control.modified" = "#0184BCCC"
"palette.background" = "#EAEAEB"
"palette.foreground" = "$black"
"palette.current.background" = "#DBDBDC"
"palette.current.foreground" = "$black"
"tooltip.background" = "$primary-background"
"tooltip.foreground" = "$text"
"completion.background" = "#EAEAEB"
"completion.current" = "#DBDBDC"
"palette.background" = "$secondary-background"
"palette.foreground" = "$text"
"palette.current.background" = "$current-background"
"palette.current.foreground" = "$text"
"hover.background" = "#EAEAEB"
"completion.background" = "$secondary-background"
"completion.current" = "$current-background"
"activity.background" = "#EAEAEB"
"activity.current" = "$white"
"hover.background" = "$secondary-background"
"activity.background" = "$secondary-background"
"activity.current" = "$primary-background"
"debug.breakpoint" = "$red"
"debug.breakpoint.hover" = "$grey"
"debug.breakpoint.hover" = "#E4564966"
"panel.background" = "#EAEAEB"
"panel.foreground" = "$black"
"panel.foreground.dim" = "#A0A1A7"
"panel.current.background" = "#DBDBDC"
"panel.current.foreground" = "$black"
"panel.current.foreground.dim" = "#A0A1A7"
"panel.background" = "$secondary-background"
"panel.foreground" = "$text"
"panel.foreground.dim" = "$dim-text"
"panel.current.background" = "$current-background"
"panel.current.foreground" = "$text"
"panel.current.foreground.dim" = "$dim-text"
"panel.hovered.background" = "#CBCBCB"
"panel.hovered.active.background" = "#A0A1A7"
"panel.hovered.foreground" = "$black"
"panel.hovered.foreground.dim" = "#A0A1A7"
"panel.hovered.active.background" = "$dim-text"
"panel.hovered.foreground" = "$text"
"panel.hovered.foreground.dim" = "$dim-text"
"status.background" = "#EAEAEB"
"status.foreground" = "$black"
"status.background" = "$secondary-background"
"status.foreground" = "$text"
"status.modal.normal.background" = "$blue"
"status.modal.normal.foreground" = "$white"
"status.modal.insert.background" = "$red"
+15 -5
View File
@@ -4,16 +4,26 @@
# the name of this task
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
program = ""
# the program arguments, e.g. args = ["arg1", "arg2"]
args = []
# the program arguments, e.g. args = ["arg1", "arg2"], optional
# arguments = []
# current working directory, optional
# cwd = "${workspace}"
# working_directory = "${workspace}"
# enviroment variables, optional
# [configs.env]
# environment variables, optional
# [configs.environment]
# VAR1 = "VAL1"
# VAR2 = "VAL2"
# task to run before the run/debug session is started, optional
# [configs.prelaunch]
# program = "cargo"
# arguments = [
# "build",
# ]
+33 -317
View File
@@ -7,21 +7,26 @@ icon-theme = "Lapce Codicons"
custom-titlebar = true
[editor]
font-family = "Cascadia Code"
font-family = "Monospace"
font-size = 13
code-lens-font-size = 2
line-height = 1.5
smart-tab = true
tab-width = 4
show-tab = true
show-bread-crumbs = true
scroll-beyond-last-line = true
cursor-surrounding-lines = 1
wrap-style = "editor-width"
wrap-column = 80
wrap-width = 600 # px
sticky-header = true
completion-show-documentation = true
show-signature = true
signature-label-code-block = true
auto-closing-matching-pairs = true
hover-delay = 300 # ms
auto-surround = true
hover-delay = 300 # ms
modal-mode-relative-line-numbers = true
format-on-save = false
highlight-matching-brackets = true
@@ -29,6 +34,7 @@ highlight-selection-occurrences = true
highlight-scope-lines = false
autosave-interval = 0
format-on-autosave = true
normalize-line-endings = true
enable-inlay-hints = true
inlay-hint-font-family = ""
inlay-hint-font-size = 0
@@ -36,10 +42,12 @@ enable-error-lens = true
error-lens-end-of-line = true
error-lens-font-family = ""
error-lens-font-size = 0
error-lens-multiline = false
enable-completion-lens = false
enable-inline-completion = true
completion-lens-font-family = ""
completion-lens-font-size = 0
blink-interval = 500 # ms
blink-interval = 500 # ms
multicursor-case-sensitive = true
multicursor-whole-words = true
render-whitespace = "none"
@@ -47,14 +55,30 @@ show-indent-guide = true
atomic-soft-tabs = false
double-click = "single"
move-focus-while-search = true
diff-context-lines=3
scroll-speed-modifier=1
diff-context-lines = 3
scroll-speed-modifier = 1
bracket-pair-colorization = false
bracket-colorization-limit = 30000
files-exclude = "**/{.git,.svn,.hg,CVS,.DS_Store,Thumbs.db}" # Glob patterns
[terminal]
font-family = ""
font-size = 0
line-height = 0
shell = ""
[terminal.default-profile]
macos = "default"
linux = "default"
windows = "default"
[terminal.profiles]
default = {}
# [terminal.profiles.example]
# command = "cargo"
# arguments = ["run"]
# environemnt = { "KEY" = "VALUE" }
# workdir = "/home/user"
[ui]
font-family = ""
@@ -65,318 +89,10 @@ status-height = 25
tab-min-width = 100
scroll-width = 10
drop-shadow-width = 0
preview-editor-width = 0
palette-width = 500
hover-font-family = ""
hover-font-size = 0
trim-search-results-whitespace = true
list-line-height = 25
[color-theme]
name = ""
[color-theme.base]
white = "#ABB2BF"
black = "#282C34"
grey = "#3E4451"
blue = "#61AFEF"
red = "#E06C75"
yellow = "#E5C07B"
orange = "#D19A66"
green = "#98C379"
purple = "#C678DD"
cyan = "#56B6C2"
magenta = "#C678DD"
[color-theme.syntax]
"comment" = "#5C6370"
"constant" = "$yellow"
"type" = "$yellow"
"typeAlias" = "$yellow"
"number" = "$yellow"
"enum" = "$yellow"
"struct" = "$yellow"
"structure" = "$yellow"
"interface" = "$yellow"
"attribute" = "$yellow"
"constructor" = "$yellow"
"function" = "$blue"
"method" = "$blue"
"function.method" = "$blue"
"keyword" = "$purple"
"keyword.control" = "$purple"
"selfKeyword" = "$purple"
"field" = "$red"
"property" = "$red"
"enumMember" = "$red"
"enum-member" = "$red"
"variable.other.member" = "$red"
"string" = "$green"
"type.builtin" = "$cyan"
"builtinType" = "$cyan"
"escape" = "$cyan"
"embedded" = "$cyan"
"symbol" = "$yellow"
[color-theme.ui]
"lapce.error" = "$red"
"lapce.warn" = "$yellow"
"lapce.dropdown_shadow" = "#000000"
"lapce.border" = "#000000"
"lapce.scroll_bar" = "#3E4451BB"
"lapce.button.primary.background" = "#50a14f"
"lapce.button.primary.foreground" = "$black"
# tab
"lapce.tab.active.background" = "$black"
"lapce.tab.active.foreground" = "$white"
"lapce.tab.active.underline" = "#528BFF"
"lapce.tab.inactive.background" = "#21252b"
"lapce.tab.inactive.foreground" = "$white"
"lapce.tab.inactive.underline" = "#528BFF77"
"lapce.tab.separator" = "#000000"
"lapce.icon.active" = "$white"
"lapce.icon.inactive" = "#5C6370"
"lapce.remote.icon" = "$black"
"lapce.remote.local" = "#4078F2"
"lapce.remote.connected" = "#50A14F"
"lapce.remote.connecting" = "#C18401"
"lapce.remote.disconnected" = "#E45649"
"lapce.plugin.name" = "#DDDDDD"
"lapce.plugin.description" = "$white"
"lapce.plugin.author" = "#B0B0B0"
"editor.background" = "$black"
"editor.foreground" = "$white"
"editor.dim" = "#5C6370"
"editor.focus" = "#CCCCCC"
"editor.caret" = "#528bFF"
"editor.selection" = "$grey"
"editor.current_line" = "#2C313C"
"editor.link" = "$blue"
"editor.visible_whitespace" = "#5C6370"
"editor.indent_guide" = "$grey"
"editor.drag_drop_background" = "#79c1fc55"
"editor.drag_drop_tab_background" = "#0b0e1455"
"editor.sticky_header_background" = "$black"
"inlay_hint.foreground" = "$white"
"inlay_hint.background" = "#528bFF88"
"error_lens.error.foreground" = "$red"
"error_lens.error.background" = "#E06C7520"
"error_lens.warning.foreground" = "$yellow"
"error_lens.warning.background" = "#E5C07B20"
"error_lens.other.foreground" = "#5C6370"
"error_lens.other.background" = "#5C637020"
"completion_lens.foreground" = "#5C6370"
"source_control.added" = "#50A14FCC"
"source_control.removed" = "#FF5266CC"
"source_control.modified" = "#0184BCCC"
"palette.background" = "#21252B"
"palette.foreground" = "$white"
"palette.current.background" = "#2C313A"
"palette.current.foreground" = "$white"
"completion.background" = "#21252B"
"completion.current" = "#2C313A"
"hover.background" = "#21252B"
"activity.background" = "#21252B"
"activity.current" = "$black"
"debug.breakpoint" = "$red"
"debug.breakpoint.hover" = "$grey"
"panel.background" = "#21252B"
"panel.foreground" = "$white"
"panel.foreground.dim" = "#5C6370"
"panel.current.background" = "#2C313A"
"panel.current.foreground" = "$white"
"panel.current.foreground.dim" = "#5C6370"
"panel.hovered.background" = "#343A45"
"panel.hovered.active.background" = "#5C6370"
"panel.hovered.foreground" = "$white"
"panel.hovered.foreground.dim" = "#5C6370"
"status.background" = "#21252B"
"status.foreground" = "$white"
"status.modal.normal.background" = "$blue"
"status.modal.normal.foreground" = "$black"
"status.modal.insert.background" = "$red"
"status.modal.insert.foreground" = "$black"
"status.modal.visual.background" = "$yellow"
"status.modal.visual.foreground" = "$black"
"status.modal.terminal.background" = "$purple"
"status.modal.terminal.foreground" = "$black"
"markdown.blockquote" = "#898989"
"terminal.cursor" = "$white"
"terminal.foreground" = "$white"
"terminal.background" = "$black"
"terminal.white" = "$white"
"terminal.black" = "$black"
"terminal.red" = "$red"
"terminal.blue" = "$blue"
"terminal.green" = "$green"
"terminal.yellow" = "$yellow"
"terminal.cyan" = "$cyan"
"terminal.magenta" = "$magenta"
"terminal.bright_white" = "#C8CCD4"
"terminal.bright_red" = "$red"
"terminal.bright_blue" = "$blue"
"terminal.bright_green" = "$green"
"terminal.bright_yellow" = "$yellow"
"terminal.bright_cyan" = "$cyan"
"terminal.bright_magenta" = "$magenta"
"terminal.bright_black" = "#545862"
[icon-theme]
name = ""
[icon-theme.ui]
"logo" = "lapce_logo.svg"
"link" = "link.svg"
"error" = "error.svg"
"add" = "add.svg"
"close" = "close.svg"
"remote" = "lapce_remote.svg"
"unsaved" = "circle-filled.svg"
"warning" = "warning.svg"
"problem" = "problem.svg"
"debug" = "debug.svg"
"debug_breakpoint" = "circle-filled.svg"
"debug_alt" = "debug-alt.svg"
"debug_small" = "debug-alt-small.svg"
"debug_restart" = "debug-restart.svg"
"debug_continue" = "debug-continue.svg"
"debug_pause" = "debug-pause.svg"
"debug_stop" = "debug-stop.svg"
"debug_console" = "debug-console.svg"
"debug_disconnect" = "debug-disconnect.svg"
"start" = "debug-start.svg"
"run_errors" = "run-errors.svg"
"settings" = "settings-gear.svg"
"terminal" = "terminal.svg"
"lightbulb" = "lightbulb.svg"
"extensions" = "extensions.svg"
"keyboard" = "keyboard.svg"
"breadcrumb_separator" = "chevron-right.svg"
"window.close" = "chrome-close.svg"
"window.restore" = "chrome-restore.svg"
"window.maximize" = "chrome-maximize.svg"
"window.minimize" = "chrome-minimize.svg"
"file" = "file.svg"
"file_explorer" = "files.svg"
"file_picker_up" = "arrow-up.svg"
"image_loading" = "refresh.svg"
"image_error" = "error.svg"
"scm.icon" = "source-control.svg"
"scm.diff.modified" = "diff-modified.svg"
"scm.diff.added" = "diff-added.svg"
"scm.diff.removed" = "diff-removed.svg"
"scm.diff.renamed" = "diff-renamed.svg"
"scm.change.add" = "add.svg"
"scm.change.remove" = "remove.svg"
"palette.menu" = "chevron-down.svg"
"fold" = "fold.svg"
"fold.up" = "fold-up.svg"
"fold.down" = "fold-down.svg"
"dropdown.arrow" = "chevron-down.svg"
"location.forward" = "arrow-right.svg"
"location.backward" = "arrow-left.svg"
"item.opened" = "chevron-down.svg"
"item.closed" = "chevron-right.svg"
"directory.closed" = "folder.svg"
"directory.opened" = "folder-opened.svg"
"panel.restore" = "chevron-down.svg"
"panel.maximise" = "chevron-right.svg"
"split.horizontal" = "split-horizontal.svg"
"tab.previous" = "chevron-left.svg"
"tab.next" = "chevron-right.svg"
"sidebar.left.on" = "layout-sidebar-left.svg"
"sidebar.left.off" = "layout-sidebar-left-off.svg"
"sidebar.right.on" = "layout-sidebar-right.svg"
"sidebar.right.off" = "layout-sidebar-right-off.svg"
"layout.panel.on" = "layout-panel.svg"
"layout.panel.off" = "layout-panel-off.svg"
"search.icon" = "search.svg"
"search.clear" = "close.svg"
"search.forward" = "arrow-down.svg"
"search.backward" = "arrow-up.svg"
"search.case_sensitive" = "case-sensitive.svg"
"search.whole_word" = "whole-word.svg"
"search.regex" = "regex.svg"
"search.replace" = "replace.svg"
"search.replace_all" = "replace-all.svg"
"symbol_kind.array" = "symbol-array.svg"
"symbol_kind.boolean" = "symbol-boolean.svg"
"symbol_kind.class" = "symbol-class.svg"
"symbol_kind.constant" = "symbol-constant.svg"
"symbol_kind.enum_member" = "symbol-enum-member.svg"
"symbol_kind.enum" = "symbol-enum.svg"
"symbol_kind.event" = "symbol-event.svg"
"symbol_kind.field" = "symbol-field.svg"
"symbol_kind.file" = "symbol-file.svg"
"symbol_kind.function" = "symbol-method.svg"
"symbol_kind.interface" = "symbol-interface.svg"
"symbol_kind.key" = "symbol-key.svg"
"symbol_kind.method" = "symbol-method.svg"
"symbol_kind.namespace" = "symbol-namespace.svg"
"symbol_kind.number" = "symbol-numeric.svg"
"symbol_kind.object" = "symbol-namespace.svg"
"symbol_kind.operator" = "symbol-operator.svg"
"symbol_kind.property" = "symbol-property.svg"
"symbol_kind.string" = "symbol-string.svg"
"symbol_kind.struct" = "symbol-structure.svg"
"symbol_kind.type_parameter" = "symbol-parameter.svg"
"symbol_kind.variable" = "symbol-variable.svg"
"completion_item_kind.class" = "symbol-class.svg"
"completion_item_kind.constant" = "symbol-constant.svg"
"completion_item_kind.enum_member" = "symbol-enum-member.svg"
"completion_item_kind.enum" = "symbol-enum.svg"
"completion_item_kind.field" = "symbol-field.svg"
"completion_item_kind.function" = "symbol-method.svg"
"completion_item_kind.interface" = "symbol-interface.svg"
"completion_item_kind.keyword" = "symbol-keyword.svg"
"completion_item_kind.method" = "symbol-method.svg"
"completion_item_kind.module" = "symbol-namespace.svg"
"completion_item_kind.property" = "symbol-property.svg"
"completion_item_kind.snippet" = "symbol-snippet.svg"
"completion_item_kind.string" = "symbol-string.svg"
"completion_item_kind.struct" = "symbol-structure.svg"
"completion_item_kind.variable" = "symbol-variable.svg"
[icon-theme.foldername]
[icon-theme.filename]
[icon-theme.extension]
tab-close-button = "Right"
open-editors-visible = true
+232
View File
@@ -0,0 +1,232 @@
variable "RUST_VERSION" {
default = "1.76"
}
variable "XX_VERSION" {
default = "master"
}
variable "PACKAGE_NAME" {
default = RELEASE_TAG_NAME == "nightly" ? "lapce-nightly" : "lapce"
}
variable "RELEASE_TAG_NAME" {}
target "_common" {
context = "."
platforms = ["local"]
output = ["target"]
args = {
PACKAGE_NAME = PACKAGE_NAME
RELEASE_TAG_NAME = RELEASE_TAG_NAME
RUST_VERSION = RUST_VERSION
XX_VERSION = XX_VERSION
}
}
variable "platforms" {
default = [
"linux/amd64",
"linux/arm64",
]
}
target "_platforms" {
platforms = platforms
}
group "default" {
targets = ["binary"]
}
target "binary" {
inherits = ["_common"]
target = "binary"
args = {
ZSTD_SYS_USE_PKG_CONFIG = "1"
LIBGIT2_STATIC = "1"
LIBSSH2_STATIC = "1"
LIBZ_SYS_STATIC = "1"
OPENSSL_STATIC = "1"
OPENSSL_NO_VENDOR = "0"
PKG_CONFIG_ALL_STATIC = "1"
}
}
target "cross-binary" {
inherits = ["binary", "_platforms"]
target = "cross-binary"
}
target "package" {
inherits = ["_common"]
target = "package"
args = {
ZSTD_SYS_USE_PKG_CONFIG = "1"
LIBGIT2_STATIC = "0"
LIBSSH2_STATIC = "0"
LIBZ_SYS_STATIC = "0"
OPENSSL_STATIC = "0"
OPENSSL_NO_VENDOR = "1"
PKG_CONFIG_ALL_STATIC = "0"
}
}
target "cross-package" {
inherits = ["package", "_platforms"]
target = "cross-package"
}
// OS
variable "DPKG_FAMILY_PACKAGES" {
default = [
"libc6-dev",
"libssl-dev",
"zlib1g-dev",
"libzstd-dev",
"libvulkan-dev",
"libwayland-dev",
"libxcb-shape0-dev",
"libxcb-xfixes0-dev",
"libxkbcommon-x11-dev",
]
}
target "debian" {
inherits = [build.type]
name = "${os_name}-${build.os_version}-${build.type}"
dockerfile = "extra/linux/docker/${os_name}/Dockerfile"
args = {
DISTRIBUTION_NAME = os_name
DISTRIBUTION_VERSION = build.os_version
DISTRIBUTION_PACKAGES = join(" ", coalesce(build.packages, DPKG_FAMILY_PACKAGES))
}
platforms = coalesce(build.platforms, platforms)
matrix = {
os_name = ["debian"]
build = [
{ packages = null, platforms = null, type = "package", os_version = "bullseye" }, # 11
{ packages = null, platforms = null, type = "package", os_version = "bookworm" }, # 12
]
}
}
target "cross-debian" {
inherits = ["debian", "cross-package"]
}
target "ubuntu" {
inherits = [build.type]
name = "${os_name}-${build.os_version}-${build.type}"
dockerfile = "extra/linux/docker/${os_name}/Dockerfile"
args = {
DISTRIBUTION_NAME = os_name
DISTRIBUTION_VERSION = build.os_version
DISTRIBUTION_PACKAGES = join(" ", coalesce(build.packages, DPKG_FAMILY_PACKAGES))
}
platforms = coalesce(build.platforms, platforms)
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
# 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
{ packages = null, platforms = null, type = "binary", os_version = "focal" }, # 20.04
]
}
}
target "cross-ubuntu" {
inherits = ["ubuntu", "cross-package"]
}
variable "RHEL_FAMILY_PACKAGES" {
default = [
"openssl-devel",
"wayland-devel",
"vulkan-loader-devel",
"libzstd-devel",
"libxcb-devel",
"libxkbcommon-x11-devel",
]
}
target "fedora" {
inherits = [build.type]
name = "${name}-${build.version}-${build.type}"
dockerfile = "extra/linux/docker/${name}/Dockerfile"
args = {
XX_VERSION = "test"
DISTRIBUTION_NAME = name
DISTRIBUTION_VERSION = build.version
DISTRIBUTION_PACKAGES = join(" ", coalesce(build.packages, RHEL_FAMILY_PACKAGES))
}
// platforms = coalesce(build.platforms, platforms)
platforms = ["linux/amd64"]
matrix = {
name = ["fedora"]
build = [
{ 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 = "rawhide" },
]
}
}
target "cross-fedora" {
inherits = ["fedora", "cross-package"]
}
variable "APK_FAMILY_PACKAGES" {
default = [
"make",
"clang",
"git",
"lld",
"build-base",
"rustup",
"openssl-libs-static",
"libssh2-static",
"libgit2-static",
"fontconfig-static",
"freetype-static",
"zlib-static",
"gcc",
"zstd-static",
"libxcb-static",
"libxkbcommon-static",
"vulkan-loader-dev",
]
}
target "alpine" {
inherits = ["binary"]
name = format("${os_name}-%s", replace(build.os_version, ".", "-"))
dockerfile = "extra/linux/docker/${os_name}/Dockerfile"
args = {
DISTRIBUTION_NAME = os_name
DISTRIBUTION_VERSION = build.os_version
DISTRIBUTION_PACKAGES = join(" ", coalesce(build.packages, APK_FAMILY_PACKAGES))
}
platforms = coalesce(build.platforms, platforms)
matrix = {
os_name = ["alpine"]
build = [
{ os_version = "3.18", packages = null, platforms = null },
]
}
}
target "cross-alpine" {
inherits = ["alpine-3-18", "cross-binary"]
}
+17 -5
View File
@@ -8,15 +8,15 @@ It is easy to build Lapce from source on a GNU/Linux distribution. Cargo handles
#### Ubuntu
```sh
sudo apt install clang libxkbcommon-x11-dev pkg-config libvulkan-dev
sudo apt install clang libxkbcommon-x11-dev pkg-config libvulkan-dev libwayland-dev xorg-dev libxcb-shape0-dev libxcb-xfixes0-dev
```
#### Fedora
```sh
sudo dnf install clang libxkbcommon-x11-devel libxcb-devel vulkan-loader-devel
sudo dnf install clang libxkbcommon-x11-devel libxcb-devel vulkan-loader-devel wayland-devel openssl-devel pkgconf
```
#### Void Linux
```sh
sudo xbps-install -S base-devel clang libxkbcommon-devel vulkan-loader
sudo xbps-install -S base-devel clang libxkbcommon-devel vulkan-loader wayland-devel
```
3. Clone this repository (this command will clone to your home directory):
@@ -30,9 +30,21 @@ cd ~/lapce
```
```sh
cargo build --release
cargo install --path . --bin lapce --profile release-lto --locked
```
> If you use a different distribution, and are having trouble finding appropriate dependencies, let us know in an issue!
Once Lapce is compiled, the executable will be available in `target/release/lapce`.
Once Lapce is compiled, the executable will be available in `$HOME/.cargo/bin/lapce` and should be available in `PATH` automatically.
## Building using Docker or Podman
Packages available in releases are built using containers based on multi-stage Dockerfiles. To easily orchestrate builds, there is a `docker-bake.hcl` manifest in root of repository that defines all stages and targets.
If you want to build all packages for ubuntu, you can run `RELEASE_TAG_NAME=nightly docker buildx bake ubuntu` (`RELEASE_TAG_NAME` is a required environment variable used to tell what kind of release is being built as well as baking in the version itself).
To scope in to specific distribution version, you can define target with it's version counterpart from matrix, e.g. to build only Ubuntu Focal package, you can run `RELEASE_TAG_NAME=nightly docker buildx bake ubuntu-focal`.
Additionally to building multiple OS versions at the same time, Docker-based builds will also try to cross-compile Lapce for other architectures.
This does not require QEMU installed as it's done via true cross-compilation meaning `HOST` will run your native OS/CPU architecture and `TARGET` will be the wanted architecture, instead of spawning container that's running OS using `TARGET` architecture.
> ![WARNING]
> Do not run plain targets like `ubuntu` or `fedora` if you don't have very powerful machine, as it will spawn many concurrent jobs
> which will take a long time to build.
+13 -80
View File
@@ -1,87 +1,20 @@
## Installation With Package Manager
### Arch Linux
Lapce is available in below software repositories:
There is an community package that can be installed with `pacman`:
[![Packaging status](https://repology.org/badge/vertical-allrepos/lapce.svg)](https://repology.org/project/lapce/versions)
Lapce is also additionally available via:
- [Flatpak](https://flathub.org/apps/details/dev.lapce.lapce)
- [Scoop](https://scoop.sh/#/apps?q=lapce)
For Fedora Linux, Lapce is (unofficially) available via dnf RPM package manager, using nightly as of 2024.01:
- [copr](https://copr.fedorainfracloud.org/coprs/titaniumtown/lapce/)
```bash
sudo pacman -Syu lapce
```
### Fedora
```bash
sudo dnf copr enable titaniumtown/lapce
sudo dnf copr enable titaniumtown/lapce
sudo dnf install lapce
```
### Flatpak
Lapce is available as a flatpak [here](https://flathub.org/apps/details/dev.lapce.lapce)
```bash
flatpak install flathub dev.lapce.lapce
```
### Gentoo
Lapce is available in Gentoos user repository GURU.
If the GURU is not activated, it can be with:
```bash
emerge --ask app-eselect/eselect-repository # install eselect repository
eselect repository enable guru
emaint sync -r guru
```
After activating and syncing the GURU repository, lapce can be installed with
```bash
emerge app-editors/lapce
```
### Homebrew
```bash
brew install lapce
```
### nixpkgs
You can find the packages [here](https://search.nixos.org/packages?channel=unstable&show=lapce&from=0&size=50&sort=relevance&type=packages&query=lapce):
```bash
# try with nix-shell
nix-shell -p lapce
# on NixOS
nix-env -iA nixos.lapce
# on non-NixOS installs, including macOS
nix-env -iA nixpkgs.lapce
# only if `nix.settings.experimental-features` includes both `nix-command` and `flakes`.
# WARNING: THIS BREAKS nix-env, PROCEED AT YOUR OWN RISK. THIS ALSO INSTALLS FROM UNSTABLE BRANCH.
nix profile install nixpkgs#lapce
```
### Scoop
```bash
scoop install lapce
```
### Void Linux
```bash
sudo xbps-install -S lapce
```
### winget
You can find the packages [here](https://github.com/microsoft/winget-pkgs/tree/master/manifests/l/Lapce/Lapce):
```bash
winget install lapce
```
-23
View File
@@ -1,23 +0,0 @@
# New Contributor Guide **Developers**
This document contains some useful resources for anyone wishing to contribute to the Lapce project by contributing code.
## Quick Start
1. Get an overview of the architecture from [`docs/why-lapce.md`](why-lapce.md) and [Architecture](https://docs.lapce.dev/development/architecture).
2. Explore the rope data structure, a key part of Lapce's architecture. The crate used is [xi_rope](https://crates.io/crates/xi-rope).
3. Review both open and closed [issues](https://github.com/lapce/lapce/issues), there may be others working on a similar feature.
4. [Get cracking](https://dictionary.cambridge.org/dictionary/english/get-cracking)!
5. Be aware that unless you explicitly state otherwise, contributions you make to this project will be under the Apache License Version 2.
## Resources
The [Wikipedia article on ropes](https://en.wikipedia.org/wiki/Rope_(data_structure)) provides an overview of the data structure.
For some context, also see [Ropes: an Alternative to Strings](https://www.cs.tufts.edu/comp/150FP/archive/hans-boehm/ropes.pdf) by Boehm, Atkinson, and Plass.
-23
View File
@@ -1,23 +0,0 @@
# New Contributor Guide **Users**
This document contains some useful resources for anyone wishing to contribute to the Lapce project by suggesting improvements, requesting features, or supporting the community. Where it says _question_ in this document, it can also apply to _suggestions_, _requests_, etc.
## Quick Start
1. Open a text editor
2. Write out your question (or request, or suggestion).
3. _Revise_ your question
4. Choose where to post your question
5. Post it!
## Resources
For general information about open source software, [see these FAQs](https://opensource.org/faq) by the Open Source Initiative.
We don't have a specified code of conduct yet, but for some reasonable guidelines see the [Mozilla Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/).
[This guide](http://www.catb.org/~esr/faqs/smart-questions.html), by Eric Steven Raymond, is about how to ask excellent questions. You can apply the same techniques (such as _Use meaningful, specific subject headers_) to suggestions and bug reports. The more specific (and helpful!) you can be with your question, the more it helps us to provide a solution.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 KiB

After

Width:  |  Height:  |  Size: 944 KiB

+1 -1
View File
@@ -9,7 +9,7 @@ GenericName=Code Editor
StartupWMClass=lapce
Icon=dev.lapce.lapce
Exec=lapce
Exec=lapce %F
Terminal=false
MimeType=text/plain;inode/directory;
Actions=new-window;
+1 -1
View File
@@ -30,6 +30,6 @@
</screenshot>
</screenshots>
<releases>
<release version="0.2.8" date="2023-06-01"/>
<release version="0.4.0" date="2024-04-24"/>
</releases>
</component>
+144
View File
@@ -0,0 +1,144 @@
# syntax=docker/dockerfile:1
ARG DISTRIBUTION_VERSION
ARG RUST_VERSION
ARG XX_VERSION=latest
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine${DISTRIBUTION_VERSION} AS build-base
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 \
<<EOF
apk upgrade -Ua
apk add --no-cache \
bash clang lld llvm file git sccache \
cmake pkgconf build-base abuild \
${DISTRIBUTION_PACKAGES}
EOF
WORKDIR /source
COPY --link . .
FROM build-base AS build-prep
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL='sparse'
ENV CARGO_TARGET_DIR='/root/.cache/rust'
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=locked \
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
xx-cargo fetch --locked
EOF
# Install target dependencies
ARG TARGETPLATFORM
ARG DISTRIBUTION_PACKAGES
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=private \
--mount=type=cache,target=/var/lib/apt,sharing=private \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
xx-apk add \
"xx-cxx-essentials" \
${DISTRIBUTION_PACKAGES}
EOF
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 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 \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
--mount=type=cache,target=/root/.cache,sharing=private \
<<EOF
#!/usr/bin/env bash
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"
export PKG_CONFIG="$(xx-clang --print-prog-name=pkg-config)"
export CARGO_BUILD_TARGET="$(xx-cargo --print-target-triple)"
xx-cargo build \
--frozen \
--package lapce-app \
--profile release-lto \
--no-default-features \
--features all-languages
export RUSTFLAGS="${RUSTFLAGS} -C target-feature=+crt-static"
xx-cargo build
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/
cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2 | tee /target/lapce.version
EOF
WORKDIR /output
RUN <<EOF
#!/usr/bin/env bash
set -euxo pipefail
echo 'a'
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 .
# FROM scratch AS cross-package
# COPY --from=build /output/*.deb .
+187
View File
@@ -0,0 +1,187 @@
# syntax=docker/dockerfile:1
ARG DISTRIBUTION_VERSION
ARG RUST_VERSION
ARG XX_VERSION=latest
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-${DISTRIBUTION_VERSION} AS build-base
COPY --from=xx / /
SHELL [ "/bin/bash", "-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 \
<<EOF
apt-get update -y
apt-get install -y \
bash clang lld llvm file cmake pkg-config dpkg-dev \
${DISTRIBUTION_PACKAGES}
EOF
WORKDIR /source
COPY --link . .
FROM build-base AS build-prep
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL='sparse'
ENV CARGO_TARGET_DIR='/root/.cache/rust'
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=locked \
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
cargo fetch --locked
EOF
# Install target dependencies
ARG TARGETPLATFORM
ARG DISTRIBUTION_PACKAGES
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=private \
--mount=type=cache,target=/var/lib/apt,sharing=private \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
xx-apt-get install -y \
"xx-cxx-essentials" \
${DISTRIBUTION_PACKAGES}
EOF
FROM build-prep AS build
ARG PACKAGE_NAME
ENV PACKAGE_NAME="${PACKAGE_NAME}"
ARG OUTPUT_DIR="/output"
ENV OUTPUT_DIR="${OUTPUT_DIR}"
# in bullseye arm64 target does not link with lld so configure it to use ld instead
# RUN [ ! -f /etc/alpine-release ] && xx-info is-cross && [ "$(xx-info arch)" = "arm64" ] && XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple || true
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}"
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=locked \
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
--mount=type=cache,target=/root/.cache,sharing=private \
<<EOF
#!/usr/bin/env bash
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"
export PKG_CONFIG="$(xx-clang --print-prog-name=pkg-config)"
export CARGO_BUILD_TARGET="$(xx-cargo --print-target-triple)"
xx-cargo build \
--frozen \
--package lapce-app \
--profile release-lto \
--no-default-features \
--features all-languages
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/
cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2 | tee /target/lapce.version
EOF
WORKDIR /output
RUN <<EOF
#!/usr/bin/env bash
set -euxo pipefail
export _PACKAGE_ARCHITECTURE=$(xx-info debian-arch)
mkdir -v -p ${PACKAGE_NAME}/{etc,usr/{bin,share/{applications,metainfo,pixmaps}},debian}
cd ${PACKAGE_NAME}
cp '/source/extra/linux/dev.lapce.lapce.desktop' './usr/share/applications/dev.lapce.lapce.desktop'
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'
mv '/target/lapce' './usr/bin/'
if [[ "${PACKAGE_NAME}" == "lapce" ]]; then
conflicts="lapce-nightly"
else
conflicts="lapce"
fi
case "${RELEASE_TAG_NAME}" in
nightly-*)
version=$(cat /target/lapce.version)
commit=$(echo "${RELEASE_TAG_NAME}" | cut -d'-' -f2)
# date=$(date +%Y%m%d%H%M)
RELEASE_TAG_NAME="${version}+${commit}"
;;
debug|nightly)
version=$(cat /target/lapce.version)
date=$(date +%Y%m%d%H%M)
RELEASE_TAG_NAME="${version}+${date}"
;;
*)
RELEASE_TAG_NAME="${RELEASE_TAG_NAME//v/}"
;;
esac
cat <<- EOL > debian/control
Package: ${PACKAGE_NAME}
Version: ${RELEASE_TAG_NAME}
Conflicts: ${conflicts}
Maintainer: Jakub Panek <me+debian@panekj.dev>
Architecture: ${_PACKAGE_ARCHITECTURE}
Description: Lightning-fast and Powerful Code Editor
Source: https://lapce.dev
EOL
depends=$(dpkg-shlibdeps -O -e usr/bin/lapce)
depends=$(echo "${depends}" | sed 's/shlibs:Depends=//')
echo "Depends: ${depends}" >> debian/control
mv debian DEBIAN
. /etc/os-release
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 .
FROM scratch AS cross-package
COPY --from=build /output/*.deb .
+190
View File
@@ -0,0 +1,190 @@
# syntax=docker/dockerfile:1
ARG DISTRIBUTION_VERSION
ARG RUST_VERSION
ARG XX_VERSION=latest
FROM --platform=$BUILDPLATFORM ghcr.io/panekj/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM fedora:${DISTRIBUTION_VERSION} AS build-base
COPY --from=xx --link / /
SHELL [ "/bin/bash", "-c" ]
# install host dependencies
ARG DISTRIBUTION_PACKAGES
RUN \
--mount=type=cache,target=/var/cache/dnf,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
printf "keepcache=True" >> /etc/dnf/dnf.conf
dnf update -y
dnf install -y \
bash clang lld llvm file cmake pkg-config curl git rpm-build
EOF
ENV CARGO_HOME="/cargo"
ENV RUSTUP_HOME="/rustup"
RUN \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
EOF
ENV PATH="${CARGO_HOME}/bin:${PATH}"
WORKDIR /source
FROM build-base AS build-prep
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL='sparse'
ENV CARGO_TARGET_DIR='/target'
ENV CARGO_NET_GIT_FETCH_WITH_CLI="true"
COPY --link . .
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=locked \
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
cargo fetch --locked
EOF
# Install target dependencies
ARG TARGETPLATFORM
ARG DISTRIBUTION_PACKAGES
RUN \
--mount=type=cache,target=/var/cache/dnf,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
mkdir -p /run/lock
mkdir -p /var/run/lock
XX_DEBUG_DNF=1 \
xx-dnf install -y \
"xx-cxx-essentials" \
${DISTRIBUTION_PACKAGES}
EOF
FROM build-prep AS build
ARG PACKAGE_NAME
ENV PACKAGE_NAME="${PACKAGE_NAME}"
ARG CARGO_BUILD_INCREMENTAL='false'
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}"
RUN \
--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 Druid (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)"
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
%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
ls -lahR $HOME/rpmbuild
EOF
RUN \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
mv -v $HOME/rpmbuild/RPMS/* /output/
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/*.rpm .
FROM scratch AS cross-package
COPY --from=build /output/*.rpm .
+215
View File
@@ -0,0 +1,215 @@
# syntax=docker/dockerfile:1
ARG DISTRIBUTION_VERSION
ARG RUST_VERSION
ARG XX_VERSION
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM ubuntu:${DISTRIBUTION_VERSION} AS build-base
COPY --from=xx / /
SHELL [ "/bin/bash", "-c" ]
ENV DEBIAN_FRONTEND=noninteractive
RUN \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
rm -f /etc/apt/apt.conf.d/docker-clean
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
EOF
# 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 \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
apt-get update -y
apt-get upgrade -y
apt-get install -y \
bash clang lld llvm file cmake pkg-config curl git dpkg-dev \
${DISTRIBUTION_PACKAGES}
EOF
ENV CARGO_HOME="/cargo"
ENV RUSTUP_HOME="/rustup"
RUN \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
EOF
ENV PATH="${CARGO_HOME}/bin:${PATH}"
WORKDIR /source
COPY --link . .
FROM build-base AS build-prep
ENV CARGO_TARGET_DIR='/target'
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=locked \
--mount=type=cache,target=/cargo/registry/cache,sharing=locked \
--mount=type=cache,target=/cargo/registry/index,sharing=locked \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
cargo fetch --locked
EOF
# Install target dependencies
ARG TARGETPLATFORM
ARG DISTRIBUTION_PACKAGES
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=private \
--mount=type=cache,target=/var/lib/apt,sharing=private \
<<EOF
#!/usr/bin/env bash
set -euxo pipefail
xx-apt-get install -y \
"xx-cxx-essentials" \
${DISTRIBUTION_PACKAGES}
EOF
FROM build-prep AS build
ARG PACKAGE_NAME
ARG OUTPUT_DIR="/output"
ARG CARGO_BUILD_INCREMENTAL='false'
ENV CC='xx-clang'
ENV CXX='xx-clang++'
ARG ZSTD_SYS_USE_PKG_CONFIG
ARG LIBGIT2_STATIC
ARG LIBSSH2_STATIC
ARG LIBZ_SYS_STATIC
ARG OPENSSL_STATIC
ARG OPENSSL_NO_VENDOR
ARG PKG_CONFIG_ALL_STATIC
ARG RELEASE_TAG_NAME
RUN \
--mount=type=cache,target=/cargo/git/db,sharing=shared,readonly \
--mount=type=cache,target=/cargo/registry/cache,sharing=shared,readonly \
--mount=type=cache,target=/cargo/registry/index,sharing=shared,readonly \
--mount=type=cache,target=/root/.cache,sharing=private \
<<EOF
#!/usr/bin/env bash
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"
export PKG_CONFIG="$(xx-clang --print-prog-name=pkg-config)"
export CARGO_BUILD_TARGET="$(xx-cargo --print-target-triple)"
env
xx-cargo build \
--frozen \
--package lapce-app \
--profile release-lto \
--no-default-features \
--features all-languages
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/
cargo pkgid | cut -d'#' -f2 | cut -d'@' -f2 | cut -d':' -f2 | tee /target/lapce.version
EOF
WORKDIR /output
RUN <<EOF
#!/usr/bin/env bash
set -euxo pipefail
export _PACKAGE_ARCHITECTURE=$(xx-info debian-arch)
mkdir -v -p ${PACKAGE_NAME}/{etc,usr/{bin,share/{applications,metainfo,pixmaps}},debian}
cd ${PACKAGE_NAME}
cp '/source/extra/linux/dev.lapce.lapce.desktop' './usr/share/applications/dev.lapce.lapce.desktop'
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
if [[ "${PACKAGE_NAME}" == "lapce" ]]; then
conflicts="lapce-nightly"
else
conflicts="lapce"
fi
case "${RELEASE_TAG_NAME}" in
nightly-*)
version=$(cat /target/lapce.version)
commit=$(echo "${RELEASE_TAG_NAME}" | cut -d'-' -f2)
# date=$(date +%Y%m%d%H%M)
RELEASE_TAG_NAME="${version}+${commit}"
;;
debug|nightly)
version=$(cat /target/lapce.version)
date=$(date +%Y%m%d%H%M)
RELEASE_TAG_NAME="${version}+${date}"
;;
*)
RELEASE_TAG_NAME="${RELEASE_TAG_NAME//v/}"
;;
esac
cat <<- EOL > debian/control
Package: ${PACKAGE_NAME}
Version: ${RELEASE_TAG_NAME}
Conflicts: ${conflicts}
Maintainer: Jakub Panek <me+debian@panekj.dev>
Architecture: ${_PACKAGE_ARCHITECTURE}
Description: Lightning-fast and Powerful Code Editor
Source: https://lapce.dev
EOL
depends=$(dpkg-shlibdeps -O -e usr/bin/lapce)
depends=$(echo "${depends}" | sed 's/shlibs:Depends=//')
echo "Depends: ${depends}" >> debian/control
mv debian DEBIAN
. /etc/os-release
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 .
FROM scratch AS cross-package
COPY --from=build /output/*.deb .
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.2.8</string>
<string>0.4.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
+28 -17
View File
@@ -8,21 +8,33 @@ test_cmd() {
command -v "$1" >/dev/null
}
tmp_dir='/tmp'
# proxy version
lapce_new_ver="${1}"
# proxy directory
# eval to resolve '~' into proper user dir
eval lapce_dir="'${2}'"
if [ -e "${lapce_dir}/lapce" ]; then
chmod +x "${lapce_dir}/lapce"
case "${lapce_new_ver}" in
v*)
lapce_new_version=$(echo "${lapce_new_ver}" | cut -d'v' -f2)
lapce_new_ver_tag="${lapce_new_ver}"
;;
nightly*)
lapce_new_version="${lapce_new_ver}"
lapce_new_ver_tag=$(echo ${lapce_new_ver} | cut -d '-' -f1)
;;
*)
printf 'Unknown version\n'
exit 1
;;
esac
if [ -e "${lapce_dir}/lapce" ]; then
lapce_installed_ver=$("${lapce_dir}/lapce" --version | cut -d' ' -f2)
printf '[DEBUG]: Current proxy version: %s\n' "${lapce_installed_ver}"
printf '[DEBUG]: New proxy version: %s\n' "${lapce_new_ver}"
if [ "${lapce_installed_ver}" = "${lapce_new_ver}" ]; then
printf '[DEBUG]: New proxy version: %s\n' "${lapce_new_version}"
if [ "${lapce_installed_ver}" = "${lapce_new_version}" ]; then
printf 'Proxy already exists\n'
exit 0
else
@@ -43,7 +55,8 @@ case $(uname -s) in
Linux) os_name=linux ;;
Darwin) os_name=darwin ;;
*)
printf '[ERROR] unsupported os'
printf '[ERROR] unsupported os\n'
exit 1
;;
esac
@@ -53,15 +66,16 @@ case $(uname -m) in
arm64|aarch64) arch_name=aarch64 ;;
# riscv64) arch_name=riscv64 ;;
*)
printf '[ERROR] unsupported arch'
printf '[ERROR] unsupported arch\n'
exit 1
;;
esac
printf 'Switching to "%s"\n' "${tmp_dir}"
cd "${tmp_dir}"
lapce_download_url="https://github.com/lapce/lapce/releases/download/${lapce_new_ver_tag}/lapce-proxy-${os_name}-${arch_name}.gz"
lapce_new_ver_real_tag=$(echo ${lapce_new_ver} | cut -d '-' -f1)
lapce_download_url="https://github.com/lapce/lapce/releases/download/${lapce_new_ver_real_tag}/lapce-proxy-${os_name}-${arch_name}.gz"
printf 'Creating "%s"\n' "${lapce_dir}"
mkdir -p "${lapce_dir}"
cd "${lapce_dir}"
if test_cmd 'curl'; then
# How old curl has these options? we'll find out
@@ -76,14 +90,11 @@ else
exit 1
fi
printf 'Creating "%s"\n' "${lapce_dir}"
mkdir -p "${lapce_dir}"
printf 'Decompressing gzip\n'
gzip -d "${tmp_dir}/lapce-proxy-${os_name}-${arch_name}.gz"
gzip -df "${lapce_dir}/lapce-proxy-${os_name}-${arch_name}.gz"
printf 'Moving proxy to our dir\n'
mv -v "${tmp_dir}/lapce-proxy-${os_name}-${arch_name}" "${lapce_dir}/lapce"
printf 'Renaming proxy \n'
mv -v "${lapce_dir}/lapce-proxy-${os_name}-${arch_name}" "${lapce_dir}/lapce"
printf 'Making it executable\n'
chmod +x "${lapce_dir}/lapce"
+15 -36
View File
@@ -41,41 +41,8 @@
},
"Base": {
"type": "object",
"additionalProperties": false,
"properties": {
"white": {
"type": "string"
},
"black": {
"type": "string"
},
"grey": {
"type": "string"
},
"blue": {
"type": "string"
},
"red": {
"type": "string"
},
"yellow": {
"type": "string"
},
"orange": {
"type": "string"
},
"green": {
"type": "string"
},
"purple": {
"type": "string"
},
"cyan": {
"type": "string"
},
"magenta": {
"type": "string"
}
"additionalProperties": {
"type": "string"
},
"required": [],
"title": "Base"
@@ -182,6 +149,18 @@
},
"tag": {
"type": "string"
},
"bracket.color.1": {
"type": "string"
},
"bracket.color.2": {
"type": "string"
},
"bracket.color.3": {
"type": "string"
},
"bracket.unpaired": {
"type": "string"
}
},
"required": [],
@@ -220,4 +199,4 @@
"title": "UI"
}
}
}
}
+4 -1
View File
@@ -221,6 +221,9 @@
"auto-closing-matching-pairs": {
"type": "boolean"
},
"auto-surround": {
"type": "boolean"
},
"hover-delay": {
"type": "integer"
},
@@ -330,7 +333,7 @@
"drop-shadow-width": {
"type": "integer"
},
"preview-editor-width": {
"palette-width": {
"type": "integer"
},
"hover-font-family": {
+1 -1
View File
@@ -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.2.8" Manufacturer="Lapce">
<Product Name="Lapce" Id="*" UpgradeCode="9c09a374-1135-4782-959f-2dec376a1dfa" Language="1033" Codepage="1252" Version="0.4.0" 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"/>
+1
View File
@@ -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="M8 9.532h.542l3.905-3.905-1.061-1.06-2.637 2.61V1H7.251v6.177l-2.637-2.61-1.061 1.06 3.905 3.905H8zm1.956 3.481a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"/></svg>

After

Width:  |  Height:  |  Size: 301 B

+1
View File
@@ -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="M8 1h-.542L3.553 4.905l1.061 1.06 2.637-2.61v6.177h1.498V3.355l2.637 2.61 1.061-1.06L8.542 1H8zm1.956 12.013a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"/></svg>

After

Width:  |  Height:  |  Size: 298 B

+1
View File
@@ -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="M14.25 5.75v-4h-1.5v2.542c-1.145-1.359-2.911-2.209-4.84-2.209-3.177 0-5.92 2.307-6.16 5.398l-.02.269h1.501l.022-.226c.212-2.195 2.202-3.94 4.656-3.94 1.736 0 3.244.875 4.05 2.166h-2.83v1.5h4.163l.962-.975V5.75h-.004zM8 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></svg>

After

Width:  |  Height:  |  Size: 411 B

+1
View File
@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M16 5H0V4h16v1zm0 8H0v-1h16v1zm0-4.008H0V8h16v.992z"/></svg>

After

Width:  |  Height:  |  Size: 172 B

+1
View File
@@ -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="M12.904 9.57L8.928 5.596l3.976-3.976-.619-.62L8 5.286v.619l4.285 4.285.62-.618zM3 5.62l4.072 4.07L3 13.763l.619.618L8 10v-.619L3.619 5 3 5.619z"/></svg>

After

Width:  |  Height:  |  Size: 304 B

+8
View File
@@ -0,0 +1,8 @@
<svg version="1.1" id="svg2" width="2880" height="2880" viewBox="0 0 2880 2880" xmlns="http://www.w3.org/2000/svg">
<defs id="defs6"></defs>
<g id="g10" transform="matrix(4.511777, 0, 0, -4.511777, -3433.283203, 6312.723633)" style="filter: none;">
<g id="g12" transform="scale(0.1)">
<path d="m 8680.16,13575.4 -567.42,327.6 c 0,-2068.7 -0.01,-4137.33 -0.01,-6205.98 551.01,318.11 1102.02,636.24 1653.02,954.37 V 9306.6 L 8680.16,8679.83 c 0,1631.87 0.02,3263.67 0,4895.57 z M 9768.37,9981 v 2310.9 c 861.33,-497.3 1722.73,-994.6 2584.03,-1491.9 -672.2,-388.1 -1344.4,-776.2 -2016.6,-1164.29 V 8980.5 c 861.3,497.3 1722.7,994.6 2584,1491.9 l 567.5,327.6 -567.5,327.6 c -1050.5,606.5 -2100.9,1213 -3151.43,1819.5 l -2.62,1.5 -567.43,327.6 c 0,-1208.1 0.01,-2416.2 0.01,-3624.32 z m 1447.33,835.6 -879.9,508.1 v -1016.1 z" style="fill:#1a1a1a;fill-opacity:1;fill-rule:evenodd;stroke:none" id="path14"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 944 B

+86 -64
View File
@@ -1,69 +1,85 @@
[package]
name = "lapce-app"
version.workspace = true
authors.workspace = true
edition.workspace = true
name = "lapce-app"
license = { workspace = true }
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
alacritty_terminal.workspace = true
anyhow.workspace = true
chrono.workspace = true
clap.workspace = true
crossbeam-channel.workspace = true
directories.workspace = true
flate2.workspace = true
im.workspace = true
include_dir.workspace = true
indexmap.workspace = true
interprocess.workspace = true
itertools.workspace = true
lapce-core.workspace = true
lapce-proxy.workspace = true
lapce-rpc.workspace = true
lapce-xi-rope.workspace = true
lsp-types.workspace = true
notify.workspace = true
once_cell.workspace = true
parking_lot.workspace = true
rayon.workspace = true
reqwest.workspace = true
smallvec.workspace = true
strum.workspace = true
strum_macros.workspace = true
serde.workspace = true
serde_json.workspace = true
regex.workspace = true
tar.workspace = true
thiserror.workspace = true
toml_edit.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tracing-appender.workspace = true
alacritty_terminal = { workspace = true }
anyhow = { workspace = true }
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 }
include_dir = { workspace = true }
indexmap = { workspace = true }
interprocess = { workspace = true }
itertools = { workspace = true }
lapce-core = { workspace = true }
lapce-proxy = { workspace = true }
lapce-rpc = { workspace = true }
lapce-xi-rope = { workspace = true }
dap-types = { workspace = true }
lsp-types = { workspace = true }
notify = { workspace = true }
once_cell = { workspace = true }
parking_lot = { workspace = true }
rayon = { workspace = true }
reqwest = { workspace = true }
smallvec = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
regex = { workspace = true }
tar = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
toml_edit = { workspace = true }
tracing = { workspace = true }
tracing-log = { workspace = true }
tracing-subscriber = { workspace = true }
tracing-appender = { workspace = true }
url = { workspace = true }
Inflector = "0.11.4"
open = "3.0.2"
unicode-width = "0.1.10"
fuzzy-matcher = "0.3.7"
sled = "0.34.7"
bytemuck = "1.8.0"
tokio = { version = "1.21", features = ["full"] }
futures = "0.3.26"
floem = { git = "https://github.com/lapce/floem", rev = "57865513fc0538357ef7e0b23283423443234b9c" }
# floem = { path = "../../workspaces/floem" }
config = { version = "0.13.2", default-features = false, features = ["toml"] }
structdesc = { git = "https://github.com/lapce/structdesc" }
pulldown-cmark = "0.10.3"
Inflector = "0.11.4"
open = "5.1.2"
unicode-width = "0.1.12"
nucleo = "0.5.0"
bytemuck = "1.15.0"
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"] }
percent-encoding = { version = "2.3.1" }
[dependencies.floem]
# path = "../../workspaces/floem"
git = "https://github.com/lapce/floem"
rev = "c9e3d6a131c63286dc634b27c38b95bde43ed90d"
default-features = true
features = ["editor", "serde", "default-image-formats", "rfd-async-std"]
[target.'cfg(target_os="macos")'.dependencies]
fs_extra = "1.2.0"
dmg = "0.1.1"
dmg = "0.1.1"
[target.'cfg(target_os="windows")'.dependencies]
zip = { version = "0.6.3", default-features = false, features = ["deflate"] }
[target.'cfg(windows)'.dependencies.windows-sys]
workspace = true
features = ["Win32_Foundation", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging"]
[features]
default = ["all-languages", "updater"]
default = ["all-languages", "updater"]
portable = ["lapce-core/portable"]
updater = []
updater = []
# To build lapce with only some of the supported languages, for example:
#
@@ -71,14 +87,20 @@ updater = []
# --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-rust",
"lapce-core/lang-toml",
"lapce-core/lang-yaml",
"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",
]
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "visual_line"
harness = false
+278
View File
@@ -0,0 +1,278 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use floem::{
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout, Wrap},
reactive::Scope,
views::editor::{
layout::TextLayoutLine,
phantom_text::PhantomTextLine,
visual_line::{
ConfigId, FontSizeCacheId, LineFontSizeProvider, Lines, ResolvedWrap,
TextLayoutProvider, VLine,
},
},
};
use lapce_core::{
buffer::rope_text::{RopeText, RopeTextRef},
cursor::CursorAffinity,
};
use lapce_xi_rope::Rope;
const FONT_SIZE: usize = 12;
// TODO: use the editor data view structures!
struct TLProv<'a> {
text: &'a Rope,
phantom: HashMap<usize, PhantomTextLine>,
font_family: Vec<FamilyOwned>,
has_multiline_phantom: bool,
}
impl<'a> TextLayoutProvider for TLProv<'a> {
fn text(&self) -> Rope {
self.text.clone()
}
// An implementation relatively close to the actual new text layout impl but simplified.
// TODO(minor): It would be nice to just use the same impl as view's
fn new_text_layout(
&self,
line: usize,
font_size: usize,
wrap: ResolvedWrap,
) -> Arc<TextLayoutLine> {
let rope_text = RopeTextRef::new(self.text);
let line_content_original = rope_text.line_content(line);
// Get the line content with newline characters replaced with spaces
// and the content without the newline characters
let (line_content, _line_content_original) =
if let Some(s) = line_content_original.strip_suffix("\r\n") {
(
format!("{s} "),
&line_content_original[..line_content_original.len() - 2],
)
} else if let Some(s) = line_content_original.strip_suffix('\n') {
(
format!("{s} ",),
&line_content_original[..line_content_original.len() - 1],
)
} else {
(
line_content_original.to_string(),
&line_content_original[..],
)
};
let phantom_text = self.phantom.get(&line).cloned().unwrap_or_default();
let line_content = phantom_text.combine_with_text(&line_content);
let attrs = Attrs::new()
.family(&self.font_family)
.font_size(font_size as f32);
let mut attrs_list = AttrsList::new(attrs);
// We don't do line styles, since they aren't relevant
// Apply phantom text specific styling
for (offset, size, col, phantom) in phantom_text.offset_size_iter() {
let start = col + offset;
let end = start + size;
let mut attrs = attrs;
if let Some(fg) = phantom.fg {
attrs = attrs.color(fg);
}
if let Some(phantom_font_size) = phantom.font_size {
attrs = attrs.font_size(phantom_font_size.min(font_size) as f32);
}
attrs_list.add_span(start..end, attrs);
// if let Some(font_family) = phantom.font_family.clone() {
// layout_builder = layout_builder.range_attribute(
// start..end,
// TextAttribute::FontFamily(font_family),
// );
// }
}
let mut text_layout = TextLayout::new();
text_layout.set_wrap(Wrap::Word);
match wrap {
// We do not have to set the wrap mode if we do not set the width
ResolvedWrap::None => {}
ResolvedWrap::Column(_col) => todo!(),
ResolvedWrap::Width(px) => {
text_layout.set_size(px, f32::MAX);
}
}
text_layout.set_text(&line_content, attrs_list);
// skip phantom text background styling because it doesn't shift positions
// skip severity styling
// skip diagnostic background styling
Arc::new(TextLayoutLine {
extra_style: Vec::new(),
text: text_layout,
whitespaces: None,
indent: 0.0,
phantom_text: PhantomTextLine::default(),
})
}
fn before_phantom_col(&self, line: usize, col: usize) -> usize {
if let Some(phantom) = self.phantom.get(&line) {
phantom.before_col(col)
} else {
col
}
}
fn has_multiline_phantom(&self) -> bool {
self.has_multiline_phantom
}
}
struct TestFontSize;
impl LineFontSizeProvider for TestFontSize {
fn font_size(&self, _line: usize) -> usize {
FONT_SIZE
}
fn cache_id(&self) -> FontSizeCacheId {
0
}
}
fn make_lines(text: &Rope, wrap: ResolvedWrap, init: bool) -> (TLProv<'_>, Lines) {
make_lines_ph(text, wrap, init, HashMap::new(), false)
}
fn make_lines_ph(
text: &Rope,
wrap: ResolvedWrap,
init: bool,
ph: HashMap<usize, PhantomTextLine>,
has_multiline_phantom: bool,
) -> (TLProv<'_>, Lines) {
// let wrap = Wrap::Word;
// let r_wrap = ResolvedWrap::Width(width);
let font_sizes = TestFontSize;
let text = TLProv {
text,
phantom: ph,
font_family: Vec::new(),
has_multiline_phantom,
};
let cx = Scope::new();
let lines = Lines::new(cx, RefCell::new(Rc::new(font_sizes)));
lines.set_wrap(wrap);
if init {
lines.init_all(0, ConfigId::new(0, 0), &text, true);
}
(text, lines)
}
fn medium_rope() -> Rope {
let mut text = String::new();
// TODO: use some actual file's content.
for i in 0..3000 {
let content = if i % 2 == 0 {
"This is a roughly typical line of text\n"
} else if i % 3 == 0 {
"\n"
} else {
"A short line\n"
};
text.push_str(content);
}
Rope::from(&text)
}
fn visual_line(c: &mut Criterion) {
let text = medium_rope();
// Should be very fast because it is trivially linear and there's no multiline phantom
c.bench_function("last vline (uninit)", |b| {
let (text_prov, lines) = make_lines(&text, ResolvedWrap::None, false);
b.iter(|| {
lines.clear_last_vline();
let last_vline = lines.last_vline(&text_prov);
black_box(last_vline);
})
});
// Unrealistic since the user will very rarely have all of the lines initialized
// Should still be fast because there's no wrapping or multiline phantom text
c.bench_function("last vline (all, no wrapping)", |b| {
let (text_prov, lines) = make_lines(&text, ResolvedWrap::None, true);
b.iter(|| {
lines.clear_last_vline();
let last_vline = lines.last_vline(&text_prov);
let _val = black_box(last_vline);
})
});
// TODO: we could precompute line count on the text layout?
// Unrealistic since the user will very rarely have all of the lines initialized
// Still decently fast, though. <1ms
c.bench_function("last vline (all, wrapping)", |b| {
let width = 100.0;
let (text_prov, lines) = make_lines(&text, ResolvedWrap::Width(width), true);
b.iter(|| {
// This should clear any other caching mechanisms that get added
lines.clear_last_vline();
let last_vline = lines.last_vline(&text_prov);
let _val = black_box(last_vline);
})
});
// Q: This seems like 1/5th the cost of last vline despite only being half the lines..
c.bench_function("vline of offset (all, wrapping)", |b| {
let width = 100.0;
let (text_prov, lines) = make_lines(&text, ResolvedWrap::Width(width), true);
// Not past the middle. If we were past the middle then it'd be just benching last vline
// calculation, which is admittedly relatively similar to this.
let offset = 1450;
b.iter(|| {
// This should clear any other caching mechanisms that get added
lines.clear_last_vline();
let vline =
lines.vline_of_offset(&text_prov, offset, CursorAffinity::Backward);
let _val = black_box(vline);
})
});
c.bench_function("offset of vline (all, wrapping)", |b| {
let width = 100.0;
let (text_prov, lines) = make_lines(&text, ResolvedWrap::Width(width), true);
let vline = VLine(3300);
b.iter(|| {
// This should clear any other caching mechanisms that get added
lines.clear_last_vline();
let offset = lines.offset_of_vline(&text_prov, vline);
let _val = black_box(offset);
})
});
// TODO: when we have the reverse search of vline for offset, we should have a separate instance where the last vline isn't cached, which would give us a range of 'worst case' (never reused, have to recopmute it everytime) and 'best case' (always reused)
// TODO: bench common operations, like a single line changing or the (equivalent of) cache rev
// updating.
}
criterion_group!(benches, visual_line);
criterion_main!(benches);
+105 -107
View File
@@ -1,11 +1,12 @@
use std::sync::Arc;
use std::rc::Rc;
use floem::{
event::EventListener,
keyboard::Modifiers,
reactive::{RwSignal, Scope},
style::{CursorStyle, Display, Position, Style},
view::View,
style::{CursorStyle, Display, Position},
views::{container, label, stack, svg, Decorators},
View,
};
use lapce_core::{command::FocusCommand, meta::VERSION, mode::Mode};
@@ -20,11 +21,11 @@ use crate::{
struct AboutUri {}
impl AboutUri {
const LAPCE: &str = "https://lapce.dev";
const GITHUB: &str = "https://github.com/lapce/lapce";
const MATRIX: &str = "https://matrix.to/#/#lapce-editor:matrix.org";
const DISCORD: &str = "https://discord.gg/n8tGJ6Rn6D";
const CODICONS: &str = "https://github.com/microsoft/vscode-codicons";
const LAPCE: &'static str = "https://lapce.dev";
const GITHUB: &'static str = "https://github.com/lapce/lapce";
const MATRIX: &'static str = "https://matrix.to/#/#lapce-editor:matrix.org";
const DISCORD: &'static str = "https://discord.gg/n8tGJ6Rn6D";
const CODICONS: &'static str = "https://github.com/microsoft/vscode-codicons";
}
#[derive(Clone)]
@@ -53,7 +54,7 @@ impl AboutData {
impl KeyPressFocus for AboutData {
fn get_mode(&self) -> Mode {
Mode::Visual
Mode::Insert
}
fn check_condition(
@@ -67,12 +68,13 @@ impl KeyPressFocus for AboutData {
&self,
command: &crate::command::LapceCommand,
_count: Option<usize>,
_mods: floem::glazier::Modifiers,
_mods: Modifiers,
) -> crate::command::CommandExecuted {
match &command.kind {
CommandKind::Workbench(_) => {}
CommandKind::Edit(_) => {}
CommandKind::Move(_) => {}
CommandKind::Scroll(_) => {}
CommandKind::Focus(cmd) => {
if cmd == &FocusCommand::ModalClose {
self.close();
@@ -91,124 +93,120 @@ impl KeyPressFocus for AboutData {
}
}
pub fn about_popup(window_tab_data: Arc<WindowTabData>) -> impl View {
pub fn about_popup(window_tab_data: Rc<WindowTabData>) -> impl View {
let about_data = window_tab_data.about_data.clone();
let config = window_tab_data.common.config;
let internal_command = window_tab_data.common.internal_command;
let logo_size = 100.0;
exclusive_popup(window_tab_data, about_data.visible, move || {
stack(move || {
(
svg(move || (*config.get()).logo_svg()).style(move || {
Style::BASE.size_px(logo_size, logo_size).color(
*config.get().get_color(LapceColor::EDITOR_FOREGROUND),
)
}),
label(|| "Lapce".to_string()).style(move || {
Style::BASE.font_bold().margin_top_px(10.0).color(
*config.get().get_color(LapceColor::EDITOR_FOREGROUND),
)
}),
label(|| format!("Version: {}", VERSION)).style(move || {
Style::BASE
.margin_top_px(10.0)
.color(*config.get().get_color(LapceColor::EDITOR_DIM))
}),
web_link(
|| "Website".to_string(),
|| AboutUri::LAPCE.to_string(),
move || *config.get().get_color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|| Style::BASE.margin_top_px(20.0)),
web_link(
|| "GitHub".to_string(),
|| AboutUri::GITHUB.to_string(),
move || *config.get().get_color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|| Style::BASE.margin_top_px(10.0)),
web_link(
|| "Discord".to_string(),
|| AboutUri::DISCORD.to_string(),
move || *config.get().get_color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|| Style::BASE.margin_top_px(10.0)),
web_link(
|| "Matrix".to_string(),
|| AboutUri::MATRIX.to_string(),
move || *config.get().get_color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|| Style::BASE.margin_top_px(10.0)),
label(|| "Attributions".to_string()).style(move || {
Style::BASE
.font_bold()
.color(*config.get().get_color(LapceColor::EDITOR_DIM))
.margin_top_px(40.0)
}),
web_link(
|| "Codicons (CC-BY-4.0)".to_string(),
|| AboutUri::CODICONS.to_string(),
move || *config.get().get_color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|| Style::BASE.margin_top_px(10.0)),
stack((
svg(move || (config.get()).logo_svg()).style(move |s| {
s.size(logo_size, logo_size)
.color(config.get().color(LapceColor::EDITOR_FOREGROUND))
}),
label(|| "Lapce".to_string()).style(move |s| {
s.font_bold()
.margin_top(10.0)
.color(config.get().color(LapceColor::EDITOR_FOREGROUND))
}),
label(|| format!("Version: {}", VERSION)).style(move |s| {
s.margin_top(10.0)
.color(config.get().color(LapceColor::EDITOR_DIM))
}),
web_link(
|| "Website".to_string(),
|| AboutUri::LAPCE.to_string(),
move || config.get().color(LapceColor::EDITOR_LINK),
internal_command,
)
})
.style(|| Style::BASE.flex_col().items_center())
.style(|s| s.margin_top(20.0)),
web_link(
|| "GitHub".to_string(),
|| AboutUri::GITHUB.to_string(),
move || config.get().color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|s| s.margin_top(10.0)),
web_link(
|| "Discord".to_string(),
|| AboutUri::DISCORD.to_string(),
move || config.get().color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|s| s.margin_top(10.0)),
web_link(
|| "Matrix".to_string(),
|| AboutUri::MATRIX.to_string(),
move || config.get().color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|s| s.margin_top(10.0)),
label(|| "Attributions".to_string()).style(move |s| {
s.font_bold()
.color(config.get().color(LapceColor::EDITOR_DIM))
.margin_top(40.0)
}),
web_link(
|| "Codicons (CC-BY-4.0)".to_string(),
|| AboutUri::CODICONS.to_string(),
move || config.get().color(LapceColor::EDITOR_LINK),
internal_command,
)
.style(|s| s.margin_top(10.0)),
))
.style(|s| s.flex_col().items_center())
})
}
fn exclusive_popup<V: View>(
window_tab_data: Arc<WindowTabData>,
fn exclusive_popup<V: View + 'static>(
window_tab_data: Rc<WindowTabData>,
visibility: RwSignal<bool>,
content: impl FnOnce() -> V,
) -> impl View {
let config = window_tab_data.common.config;
container(move || {
container(move || {
container(content)
.style(move || {
container(
container(
container(content())
.style(move |s| {
let config = config.get();
Style::BASE
.padding_vert_px(25.0)
.padding_horiz_px(100.0)
s.padding_vert(25.0)
.padding_horiz(100.0)
.border(1.0)
.border_radius(6.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.background(*config.get_color(LapceColor::PANEL_BACKGROUND))
.border_color(config.color(LapceColor::LAPCE_BORDER))
.background(config.color(LapceColor::PANEL_BACKGROUND))
})
.on_event(EventListener::PointerDown, move |_| true)
})
.style(move || Style::BASE.flex_grow(1.0).flex_row().items_center())
.hover_style(move || Style::BASE.cursor(CursorStyle::Default))
})
.on_event(EventListener::PointerDown, move |_| {
.on_event_stop(EventListener::PointerDown, move |_| {}),
)
.style(move |s| {
s.flex_grow(1.0)
.flex_row()
.items_center()
.hover(move |s| s.cursor(CursorStyle::Default))
}),
)
.on_event_stop(EventListener::PointerDown, move |_| {
window_tab_data.about_data.close();
true
})
// Prevent things behind the grayed out area from being hovered.
.on_event(EventListener::PointerMove, move |_| true)
.style(move || {
Style::BASE
.display(if visibility.get() {
Display::Flex
} else {
Display::None
})
.position(Position::Absolute)
.size_pct(100.0, 100.0)
.flex_col()
.items_center()
.background(
config
.get()
.get_color(LapceColor::LAPCE_DROPDOWN_SHADOW)
.with_alpha_factor(0.5),
)
.on_event_stop(EventListener::PointerMove, move |_| {})
.style(move |s| {
s.display(if visibility.get() {
Display::Flex
} else {
Display::None
})
.position(Position::Absolute)
.size_pct(100.0, 100.0)
.flex_col()
.items_center()
.background(
config
.get()
.color(LapceColor::LAPCE_DROPDOWN_SHADOW)
.with_alpha_factor(0.5),
)
})
}
+96 -116
View File
@@ -7,9 +7,9 @@ use std::{
use floem::{
event::EventListener,
reactive::{ReadSignal, RwSignal, Scope},
style::{CursorStyle, Style},
view::View,
views::{container, label, list, stack, svg, Decorators},
style::CursorStyle,
views::{container, dyn_stack, label, stack, svg, Decorators},
View,
};
use crate::{
@@ -41,7 +41,7 @@ pub struct AlertBoxData {
}
impl AlertBoxData {
pub fn new(cx: Scope, common: CommonData) -> Self {
pub fn new(cx: Scope, common: Rc<CommonData>) -> Self {
Self {
active: cx.create_rw_signal(false),
title: cx.create_rw_signal("".to_string()),
@@ -60,125 +60,105 @@ pub fn alert_box(alert_data: AlertBoxData) -> impl View {
let buttons = alert_data.buttons;
let button_id = AtomicU64::new(0);
container(|| {
container(|| {
stack(|| {
(
svg(move || config.get().ui_svg(LapceIcons::WARNING)).style(
move || {
Style::BASE.size_px(50.0, 50.0).color(
*config.get().get_color(LapceColor::LAPCE_WARN),
)
},
),
label(move || title.get()).style(move || {
Style::BASE
.margin_top_px(20.0)
.width_pct(100.0)
.font_bold()
.font_size((config.get().ui.font_size() + 1) as f32)
}),
label(move || msg.get()).style(move || {
Style::BASE.width_pct(100.0).margin_top_px(10.0)
}),
list(
move || buttons.get(),
move |_button| {
button_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
},
move |button| {
label(move || button.text.clone())
.on_click(move |_| {
(button.action)();
true
})
.style(move || {
let config = config.get();
Style::BASE
.margin_top_px(10.0)
.width_pct(100.0)
.justify_center()
.font_size(
(config.ui.font_size() + 1) as f32,
container({
container({
stack((
svg(move || config.get().ui_svg(LapceIcons::WARNING)).style(
move |s| {
s.size(50.0, 50.0)
.color(config.get().color(LapceColor::LAPCE_WARN))
},
),
label(move || title.get()).style(move |s| {
s.margin_top(20.0)
.width_pct(100.0)
.font_bold()
.font_size((config.get().ui.font_size() + 1) as f32)
}),
label(move || msg.get())
.style(move |s| s.width_pct(100.0).margin_top(10.0)),
dyn_stack(
move || buttons.get(),
move |_button| {
button_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
},
move |button| {
label(move || button.text.clone())
.on_click_stop(move |_| {
(button.action)();
})
.style(move |s| {
let config = config.get();
s.margin_top(10.0)
.width_pct(100.0)
.justify_center()
.font_size((config.ui.font_size() + 1) as f32)
.line_height(1.6)
.border(1.0)
.border_radius(6.0)
.border_color(
config.color(LapceColor::LAPCE_BORDER),
)
.hover(|s| {
s.cursor(CursorStyle::Pointer).background(
config.color(
LapceColor::PANEL_HOVERED_BACKGROUND,
),
)
.line_height(1.6)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get_color(LapceColor::LAPCE_BORDER),
)
})
.hover_style(move || {
Style::BASE
.cursor(CursorStyle::Pointer)
.background(*config.get().get_color(
LapceColor::PANEL_HOVERED_BACKGROUND,
))
})
.active_style(move || {
Style::BASE.background(*config.get().get_color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
})
},
)
.style(|| {
Style::BASE.flex_col().width_pct(100.0).margin_top_px(10.0)
}),
label(|| "Cancel".to_string())
.on_click(move |_| {
active.set(false);
true
})
.style(move || {
let config = config.get();
Style::BASE
.margin_top_px(20.0)
.width_pct(100.0)
.justify_center()
.font_size((config.ui.font_size() + 1) as f32)
.line_height(1.5)
.border(1.0)
.border_radius(6.0)
.border_color(
*config.get_color(LapceColor::LAPCE_BORDER),
)
})
.hover_style(move || {
Style::BASE.cursor(CursorStyle::Pointer).background(
*config
.get()
.get_color(LapceColor::PANEL_HOVERED_BACKGROUND),
)
})
.active_style(move || {
Style::BASE.background(*config.get().get_color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
}),
})
.active(|s| {
s.background(config.color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
})
})
},
)
})
.style(|| Style::BASE.flex_col().items_center().width_pct(100.0))
.style(|s| s.flex_col().width_pct(100.0).margin_top(10.0)),
label(|| "Cancel".to_string())
.on_click_stop(move |_| {
active.set(false);
})
.style(move |s| {
let config = config.get();
s.margin_top(20.0)
.width_pct(100.0)
.justify_center()
.font_size((config.ui.font_size() + 1) as f32)
.line_height(1.5)
.border(1.0)
.border_radius(6.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
.hover(|s| {
s.cursor(CursorStyle::Pointer).background(
config
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
)
})
.active(|s| {
s.background(config.color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
})
}),
))
.style(|s| s.flex_col().items_center().width_pct(100.0))
})
.on_event(EventListener::PointerDown, |_| true)
.style(move || {
.on_event_stop(EventListener::PointerDown, |_| {})
.style(move |s| {
let config = config.get();
Style::BASE
.padding_px(20.0)
.width_px(250.0)
s.padding(20.0)
.width(250.0)
.border(1.0)
.border_radius(6.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.color(*config.get_color(LapceColor::EDITOR_FOREGROUND))
.background(*config.get_color(LapceColor::PANEL_BACKGROUND))
.border_color(config.color(LapceColor::LAPCE_BORDER))
.color(config.color(LapceColor::EDITOR_FOREGROUND))
.background(config.color(LapceColor::PANEL_BACKGROUND))
})
})
.on_event(EventListener::PointerDown, move |_| true)
.style(move || {
Style::BASE
.absolute()
.on_event_stop(EventListener::PointerDown, move |_| {})
.style(move |s| {
s.absolute()
.size_pct(100.0, 100.0)
.items_center()
.justify_center()
@@ -186,7 +166,7 @@ pub fn alert_box(alert_data: AlertBoxData) -> impl View {
.background(
config
.get()
.get_color(LapceColor::LAPCE_DROPDOWN_SHADOW)
.color(LapceColor::LAPCE_DROPDOWN_SHADOW)
.with_alpha_factor(0.5),
)
})
+2807 -2172
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -1,3 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use lapce_app::app;
pub fn main() {
+7 -5
View File
@@ -1,6 +1,7 @@
use std::sync::Arc;
use std::{rc::Rc, sync::Arc};
use floem::{
keyboard::Modifiers,
peniko::kurbo::Rect,
reactive::{RwSignal, Scope},
};
@@ -48,7 +49,7 @@ pub struct CodeActionData {
pub filtered_items: im::Vector<ScoredCodeActionItem>,
pub layout_rect: Rect,
pub mouse_click: bool,
pub common: CommonData,
pub common: Rc<CommonData>,
}
impl KeyPressFocus for CodeActionData {
@@ -64,12 +65,13 @@ impl KeyPressFocus for CodeActionData {
&self,
command: &crate::command::LapceCommand,
_count: Option<usize>,
_mods: floem::glazier::Modifiers,
_mods: Modifiers,
) -> crate::command::CommandExecuted {
match &command.kind {
CommandKind::Workbench(_) => {}
CommandKind::Edit(_) => {}
CommandKind::Move(_) => {}
CommandKind::Scroll(_) => {}
CommandKind::Focus(cmd) => {
self.run_focus_command(cmd);
}
@@ -83,7 +85,7 @@ impl KeyPressFocus for CodeActionData {
}
impl CodeActionData {
pub fn new(cx: Scope, common: CommonData) -> Self {
pub fn new(cx: Scope, common: Rc<CommonData>) -> Self {
let status = cx.create_rw_signal(CodeActionStatus::Inactive);
let active = cx.create_rw_signal(0);
@@ -192,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
+114 -16
View File
@@ -1,11 +1,20 @@
use std::{path::PathBuf, sync::Arc};
use std::{path::PathBuf, rc::Rc, sync::Arc};
use floem::{peniko::kurbo::Vec2, reactive::RwSignal};
use dap_types::types::{DapId, RunDebugConfig};
pub use floem::views::editor::command::CommandExecuted;
use floem::{
keyboard::Modifiers, peniko::kurbo::Vec2, views::editor::command::Command,
};
use indexmap::IndexMap;
use lapce_core::command::{
EditCommand, FocusCommand, MotionModeCommand, MoveCommand, MultiSelectionCommand,
EditCommand, FocusCommand, MotionModeCommand, MoveCommand,
MultiSelectionCommand, ScrollCommand,
};
use lapce_rpc::{
plugin::{PluginId, VoltID},
proxy::ProxyStatus,
terminal::{TermId, TerminalProfile},
};
use lapce_rpc::{dap_types::RunDebugConfig, plugin::PluginId, terminal::TermId};
use lsp_types::{CodeActionOrCommand, Position, WorkspaceEdit};
use serde_json::Value;
use strum::{EnumMessage, IntoEnumIterator};
@@ -14,7 +23,7 @@ use strum_macros::{Display, EnumIter, EnumMessage, EnumString, IntoStaticStr};
use crate::{
alert::AlertButton,
debug::RunDebugMode,
doc::Document,
doc::Doc,
editor::location::EditorLocation,
editor_tab::EditorTabChild,
id::EditorTabId,
@@ -33,6 +42,7 @@ pub enum CommandKind {
Workbench(LapceWorkbenchCommand),
Edit(EditCommand),
Move(MoveCommand),
Scroll(ScrollCommand),
Focus(FocusCommand),
MotionMode(MotionModeCommand),
MultiSelection(MultiSelectionCommand),
@@ -44,6 +54,7 @@ impl CommandKind {
CommandKind::Workbench(cmd) => cmd.get_message(),
CommandKind::Edit(cmd) => cmd.get_message(),
CommandKind::Move(cmd) => cmd.get_message(),
CommandKind::Scroll(cmd) => cmd.get_message(),
CommandKind::Focus(cmd) => cmd.get_message(),
CommandKind::MotionMode(cmd) => cmd.get_message(),
CommandKind::MultiSelection(cmd) => cmd.get_message(),
@@ -55,17 +66,26 @@ impl CommandKind {
CommandKind::Workbench(cmd) => cmd.into(),
CommandKind::Edit(cmd) => cmd.into(),
CommandKind::Move(cmd) => cmd.into(),
CommandKind::Scroll(cmd) => cmd.into(),
CommandKind::Focus(cmd) => cmd.into(),
CommandKind::MotionMode(cmd) => cmd.into(),
CommandKind::MultiSelection(cmd) => cmd.into(),
}
}
}
#[derive(PartialEq, Eq)]
pub enum CommandExecuted {
Yes,
No,
impl From<Command> for CommandKind {
fn from(cmd: Command) -> Self {
use Command::*;
match cmd {
Edit(edit) => CommandKind::Edit(edit),
Move(movement) => CommandKind::Move(movement),
Scroll(scroll) => CommandKind::Scroll(scroll),
MotionMode(motion_mode) => CommandKind::MotionMode(motion_mode),
MultiSelection(multi_selection) => {
CommandKind::MultiSelection(multi_selection)
}
}
}
}
pub fn lapce_internal_commands() -> IndexMap<String, LapceCommand> {
@@ -95,6 +115,14 @@ pub fn lapce_internal_commands() -> IndexMap<String, LapceCommand> {
commands.insert(c.to_string(), command);
}
for c in ScrollCommand::iter() {
let command = LapceCommand {
kind: CommandKind::Scroll(c.clone()),
data: None,
};
commands.insert(c.to_string(), command);
}
for c in FocusCommand::iter() {
let command = LapceCommand {
kind: CommandKind::Focus(c.clone()),
@@ -158,6 +186,10 @@ pub enum LapceWorkbenchCommand {
#[strum(message = "Reveal Active File in File Explorer")]
RevealActiveFileInFileExplorer,
#[strum(serialize = "open_ui_inspector")]
#[strum(message = "Open Internal UI Inspector")]
OpenUIInspector,
#[strum(serialize = "change_color_theme")]
#[strum(message = "Change Color Theme")]
ChangeColorTheme,
@@ -178,6 +210,10 @@ pub enum LapceWorkbenchCommand {
#[strum(message = "Open Settings Directory")]
OpenSettingsDirectory,
#[strum(serialize = "open_theme_color_settings")]
#[strum(message = "Open Theme Color Settings")]
OpenThemeColorSettings,
#[strum(serialize = "open_keyboard_shortcuts")]
#[strum(message = "Open Keyboard Shortcuts")]
OpenKeyboardShortcuts,
@@ -270,14 +306,16 @@ pub enum LapceWorkbenchCommand {
#[strum(message = "Connect to SSH Host")]
ConnectSshHost,
#[strum(serialize = "connect_wsl")]
#[strum(message = "Connect to WSL")]
ConnectWsl,
#[cfg(windows)]
#[strum(serialize = "connect_wsl_host")]
#[strum(message = "Connect to WSL Host")]
ConnectWslHost,
#[strum(serialize = "disconnect_remote")]
#[strum(message = "Disconnect From Remote")]
DisconnectRemote,
#[strum(message = "Go To Line")]
#[strum(serialize = "palette.line")]
PaletteLine,
@@ -285,9 +323,11 @@ pub enum LapceWorkbenchCommand {
#[strum(message = "Go to File")]
Palette,
#[strum(message = "Go To Symbol In File")]
#[strum(serialize = "palette.symbol")]
PaletteSymbol,
#[strum(message = "Go To Symbol In Workspace")]
#[strum(serialize = "palette.workspace_symbol")]
PaletteWorkspaceSymbol,
@@ -307,6 +347,10 @@ pub enum LapceWorkbenchCommand {
#[strum(serialize = "palette.scm_references")]
PaletteSCMReferences,
#[strum(message = "List Palette Types")]
#[strum(serialize = "palette.palette_help")]
PaletteHelp,
#[strum(message = "Run and Debug Restart Current Running")]
#[strum(serialize = "palette.run_and_debug_restart")]
RunAndDebugRestart,
@@ -433,6 +477,10 @@ pub enum LapceWorkbenchCommand {
#[strum(message = "Change current file language")]
ChangeFileLanguage,
#[strum(serialize = "change_file_line_ending")]
#[strum(message = "Change current file line ending")]
ChangeFileLineEnding,
#[strum(serialize = "next_editor_tab")]
#[strum(message = "Next Editor Tab")]
NextEditorTab,
@@ -486,6 +534,10 @@ pub enum LapceWorkbenchCommand {
#[strum(serialize = "previous_error")]
PreviousError,
#[strum(message = "Diff Files")]
#[strum(serialize = "diff_files")]
DiffFiles,
#[strum(serialize = "quit")]
#[strum(message = "Quit Editor")]
Quit,
@@ -497,9 +549,30 @@ pub enum InternalCommand {
OpenFile {
path: PathBuf,
},
OpenFileInNewTab {
path: PathBuf,
},
MakeConfirmed,
OpenFileChanges {
path: PathBuf,
},
ReloadFileExplorer,
/// Test whether a file/directory can be created at that path
TestPathCreation {
new_path: Url,
},
FinishRenamePath {
current_path: Url,
new_path: Url,
},
FinishNewNode {
is_dir: bool,
path: Url,
},
FinishDuplicate {
source: Url,
path: Url,
},
GoToLocation {
location: EditorLocation,
},
@@ -525,6 +598,9 @@ pub enum InternalCommand {
SplitExchange {
editor_tab_id: EditorTabId,
},
NewTerminal {
profile: Option<TerminalProfile>,
},
SplitTerminal {
term_id: TermId,
},
@@ -578,12 +654,12 @@ pub enum InternalCommand {
FindEditorCommand {
command: LapceCommand,
count: Option<usize>,
mods: floem::glazier::Modifiers,
mods: Modifiers,
},
ReplaceEditorCommand {
command: LapceCommand,
count: Option<usize>,
mods: floem::glazier::Modifiers,
mods: Modifiers,
},
FocusEditorTab {
editor_tab_id: EditorTabId,
@@ -615,7 +691,29 @@ pub enum InternalCommand {
},
HideAlert,
SaveScratchDoc {
doc: RwSignal<Document>,
doc: Rc<Doc>,
},
SaveScratchDoc2 {
doc: Rc<Doc>,
},
UpdateProxyStatus {
status: ProxyStatus,
},
DapFrameScopes {
dap_id: DapId,
frame_id: usize,
},
OpenVoltView {
volt_id: VoltID,
},
ResetBlinkCursor,
OpenDiffFiles {
left_path: PathBuf,
right_path: PathBuf,
},
ExecuteProcess {
program: String,
arguments: Vec<String>,
},
}
+76 -71
View File
@@ -3,19 +3,19 @@ use std::{borrow::Cow, path::PathBuf, str::FromStr, sync::Arc};
use floem::{
peniko::kurbo::Rect,
reactive::{ReadSignal, RwSignal, Scope},
views::editor::{id::EditorId, text::Document},
};
use lapce_core::{
buffer::rope_text::RopeText, movement::Movement, rope_text_pos::RopeTextPosition,
};
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use lapce_core::{buffer::rope_text::RopeText, movement::Movement};
use lapce_rpc::{plugin::PluginId, proxy::ProxyRpcHandler};
use lsp_types::{
CompletionItem, CompletionResponse, CompletionTextEdit, InsertTextFormat,
Position,
};
use nucleo::Utf32Str;
use crate::{
config::LapceConfig, doc::Document, editor::view_data::EditorViewData,
id::EditorId, snippet::Snippet,
};
use crate::{config::LapceConfig, editor::EditorData, snippet::Snippet};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum CompletionStatus {
@@ -28,8 +28,8 @@ pub enum CompletionStatus {
pub struct ScoredCompletionItem {
pub item: CompletionItem,
pub plugin_id: PluginId,
pub score: i64,
pub label_score: i64,
pub score: u32,
pub label_score: u32,
pub indices: Vec<usize>,
}
@@ -62,7 +62,7 @@ pub struct CompletionData {
/// The editor id that was most recently used to trigger a completion.
pub latest_editor_id: Option<EditorId>,
/// Matcher for filtering the completion items
matcher: Arc<SkimMatcherV2>,
matcher: RwSignal<nucleo::Matcher>,
config: ReadSignal<Arc<LapceConfig>>,
}
@@ -80,7 +80,8 @@ impl CompletionData {
input_items: im::HashMap::new(),
filtered_items: im::Vector::new(),
layout_rect: Rect::ZERO,
matcher: Arc::new(SkimMatcherV2::default().ignore_case()),
matcher: cx
.create_rw_signal(nucleo::Matcher::new(nucleo::Config::DEFAULT)),
latest_editor_id: None,
config,
}
@@ -179,41 +180,57 @@ impl CompletionData {
// Filter the items by the fuzzy matching with the input text.
let mut items: im::Vector<ScoredCompletionItem> = self
.all_items()
.iter()
.filter_map(|i| {
let filter_text =
i.item.filter_text.as_ref().unwrap_or(&i.item.label);
let shift = i
.item
.label
.match_indices(filter_text)
.next()
.map(|(shift, _)| shift)
.unwrap_or(0);
if let Some((score, mut indices)) =
self.matcher.fuzzy_indices(filter_text, &self.input)
{
if shift > 0 {
for idx in indices.iter_mut() {
*idx += shift;
.matcher
.try_update(|matcher| {
let pattern = nucleo::pattern::Pattern::parse(
&self.input,
nucleo::pattern::CaseMatching::Ignore,
nucleo::pattern::Normalization::Smart,
);
self.all_items()
.iter()
.filter_map(|i| {
let filter_text =
i.item.filter_text.as_ref().unwrap_or(&i.item.label);
let shift = i
.item
.label
.match_indices(filter_text)
.next()
.map(|(shift, _)| shift)
.unwrap_or(0);
let mut indices = Vec::new();
let mut filter_text_buf = Vec::new();
let filter_text =
Utf32Str::new(filter_text, &mut filter_text_buf);
if let Some(score) =
pattern.indices(filter_text, matcher, &mut indices)
{
if shift > 0 {
for idx in indices.iter_mut() {
*idx += shift as u32;
}
}
let mut item = i.clone();
item.score = score;
item.label_score = score;
item.indices =
indices.into_iter().map(|i| i as usize).collect();
let mut label_buf = Vec::new();
let label_text =
Utf32Str::new(&i.item.label, &mut label_buf);
if let Some(score) = pattern.score(label_text, matcher) {
item.label_score = score;
}
Some(item)
} else {
None
}
}
let mut item = i.clone();
item.score = score;
item.label_score = score;
item.indices = indices;
if let Some(score) =
self.matcher.fuzzy_match(&i.item.label, &self.input)
{
item.label_score = score;
}
Some(item)
} else {
None
}
})
.collect()
})
.collect();
.unwrap();
// Sort all the items by their score, then their label score, then their length.
items.sort_by(|a, b| {
b.score
@@ -282,60 +299,46 @@ impl CompletionData {
/// Update the completion lens of the document with the active completion item.
pub fn update_document_completion(
&self,
view: &EditorViewData,
editor_data: &EditorData,
cursor_offset: usize,
) {
let doc = view.doc;
let doc = editor_data.doc();
if !doc.with_untracked(|doc| doc.content.is_file()) {
if !doc.content.with_untracked(|content| content.is_file()) {
return;
}
let config = self.config.get_untracked();
if !config.editor.enable_completion_lens {
clear_completion_lens(doc);
doc.clear_completion_lens();
return;
}
let completion_lens = doc.with_untracked(|doc| {
completion_lens_text(
view.rope_text(),
cursor_offset,
self,
doc.completion_lens(),
)
});
let completion_lens = completion_lens_text(
doc.rope_text(),
cursor_offset,
self,
doc.completion_lens().as_deref(),
);
match completion_lens {
Some(Some(lens)) => {
let offset = self.offset + self.input.len();
// TODO: will need to be adjusted to use visual line.
// Could just store the offset in doc.
let (line, col) = view.offset_to_line_col(offset);
let (line, col) = editor_data.editor.offset_to_line_col(offset);
doc.update(|doc| {
doc.set_completion_lens(lens, line, col);
});
doc.set_completion_lens(lens, line, col);
}
// Unchanged
Some(None) => {}
None => {
clear_completion_lens(doc);
doc.clear_completion_lens();
}
}
}
}
/// Clear the current completion lens. Only `update`s if there is a completion lens.
pub fn clear_completion_lens(doc: RwSignal<Document>) {
let has_completion = doc.with_untracked(|doc| doc.completion_lens().is_some());
if has_completion {
doc.update(|doc| {
doc.clear_completion_lens();
});
}
}
/// Get the text of the completion lens for the given completion item.
/// Returns `None` if the completion lens should be hidden.
/// Returns `Some(None)` if the completion lens should be shown, but not changed.
@@ -356,7 +359,9 @@ fn completion_lens_text(
.unwrap_or(InsertTextFormat::PLAIN_TEXT);
// We don't display insert and replace
let CompletionTextEdit::Edit(edit) = edit else { return None };
let CompletionTextEdit::Edit(edit) = edit else {
return None;
};
// The completion offset can be different from the current cursor offset.
let completion_offset = completion.offset;
+280 -127
View File
@@ -4,6 +4,7 @@ use std::{
sync::Arc,
};
use ::core::slice;
use floem::peniko::Color;
use itertools::Itertools;
use lapce_core::directory::Directory;
@@ -13,12 +14,14 @@ use lsp_types::{CompletionItemKind, SymbolKind};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use serde::Deserialize;
use strum::VariantNames;
use tracing::error;
use self::{
color::LapceColor,
color_theme::{ColorThemeConfig, ThemeColor, ThemeColorPreference},
core::CoreConfig,
editor::EditorConfig,
editor::{EditorConfig, WrapStyle, SCALE_OR_SIZE_LIMIT},
icon::LapceIcons,
icon_theme::IconThemeConfig,
svg::SvgStore,
@@ -48,6 +51,38 @@ static DEFAULT_CONFIG: Lazy<config::Config> = Lazy::new(LapceConfig::default_con
static DEFAULT_LAPCE_CONFIG: Lazy<LapceConfig> =
Lazy::new(LapceConfig::default_lapce_config);
static DEFAULT_DARK_THEME_CONFIG: Lazy<config::Config> = Lazy::new(|| {
config::Config::builder()
.add_source(config::File::from_str(
DEFAULT_DARK_THEME,
config::FileFormat::Toml,
))
.build()
.unwrap()
});
/// The default theme is the dark theme.
static DEFAULT_DARK_THEME_COLOR_CONFIG: Lazy<ColorThemeConfig> = Lazy::new(|| {
let (_, theme) =
LapceConfig::load_color_theme_from_str(DEFAULT_DARK_THEME).unwrap();
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<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")
});
/// Used for creating a `DropdownData` for a setting
#[derive(Debug, Clone)]
pub struct DropdownInfo {
@@ -56,7 +91,7 @@ pub struct DropdownInfo {
pub items: im::Vector<String>,
}
#[derive(Clone, Deserialize, Default)]
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct LapceConfig {
#[serde(skip)]
@@ -65,15 +100,13 @@ pub struct LapceConfig {
pub ui: UIConfig,
pub editor: EditorConfig,
pub terminal: TerminalConfig,
#[serde(default)]
pub color_theme: ColorThemeConfig,
#[serde(default)]
pub icon_theme: IconThemeConfig,
#[serde(flatten)]
pub plugins: HashMap<String, HashMap<String, serde_json::Value>>,
#[serde(skip)]
pub default_color_theme: ColorThemeConfig,
#[serde(skip)]
pub default_icon_theme: IconThemeConfig,
#[serde(skip)]
pub color: ThemeColor,
#[serde(skip)]
pub available_color_themes: HashMap<String, (String, config::Config)>,
@@ -90,18 +123,30 @@ pub struct LapceConfig {
color_theme_list: im::Vector<String>,
#[serde(skip)]
icon_theme_list: im::Vector<String>,
/// The couple names for the wrap style
#[serde(skip)]
wrap_style_list: im::Vector<String>,
}
impl LapceConfig {
pub fn load(workspace: &LapceWorkspace, disabled_volts: &[VoltID]) -> Self {
pub fn load(
workspace: &LapceWorkspace,
disabled_volts: &[VoltID],
extra_plugin_paths: &[PathBuf],
) -> Self {
let config = Self::merge_config(workspace, None, None);
let mut lapce_config: LapceConfig = config
.try_deserialize()
.unwrap_or_else(|_| DEFAULT_LAPCE_CONFIG.clone());
let mut lapce_config: LapceConfig = match config.try_deserialize() {
Ok(config) => config,
Err(error) => {
error!("Failed to deserialize configuration file: {error}");
DEFAULT_LAPCE_CONFIG.clone()
}
};
lapce_config.available_color_themes =
Self::load_color_themes(disabled_volts);
lapce_config.available_icon_themes = Self::load_icon_themes(disabled_volts);
Self::load_color_themes(disabled_volts, extra_plugin_paths);
lapce_config.available_icon_themes =
Self::load_icon_themes(disabled_volts, extra_plugin_paths);
lapce_config.resolve_theme(workspace);
lapce_config.color_theme_list = lapce_config
@@ -120,6 +165,13 @@ impl LapceConfig {
.collect();
lapce_config.icon_theme_list.sort();
lapce_config.wrap_style_list = im::vector![
WrapStyle::None.to_string(),
WrapStyle::EditorWidth.to_string(),
// TODO: WrapStyle::WrapColumn.to_string(),
WrapStyle::WrapWidth.to_string()
];
lapce_config.terminal.get_indexed_colors();
lapce_config
@@ -131,9 +183,13 @@ impl LapceConfig {
icon_theme_config: Option<config::Config>,
) -> config::Config {
let mut config = DEFAULT_CONFIG.clone();
if let Some(theme) = color_theme_config {
// TODO: use different color theme basis if the theme declares its color preference
// differently
config = config::Config::builder()
.add_source(config.clone())
.add_source(DEFAULT_DARK_THEME_CONFIG.clone())
.add_source(theme)
.build()
.unwrap_or_else(|_| config.clone());
@@ -170,7 +226,7 @@ impl LapceConfig {
}
LapceWorkspaceType::RemoteSSH(_) => {}
#[cfg(windows)]
LapceWorkspaceType::RemoteWSL => {}
LapceWorkspaceType::RemoteWSL(_) => {}
}
config
@@ -195,78 +251,66 @@ impl LapceConfig {
fn default_lapce_config() -> LapceConfig {
let mut default_lapce_config: LapceConfig =
DEFAULT_CONFIG.clone().try_deserialize().unwrap();
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_ICON_CONFIG.clone();
default_lapce_config.resolve_colors(None);
default_lapce_config
}
fn resolve_theme(&mut self, workspace: &LapceWorkspace) {
let mut default_lapce_config = DEFAULT_LAPCE_CONFIG.clone();
if let Some((_, color_theme_config)) = self
.available_color_themes
.get(&self.core.color_theme.to_lowercase())
{
if let Ok(mut theme_lapce_config) = config::Config::builder()
.add_source(DEFAULT_CONFIG.clone())
.add_source(color_theme_config.clone())
.build()
.and_then(|theme| theme.try_deserialize::<LapceConfig>())
{
theme_lapce_config.resolve_colors(Some(&default_lapce_config));
default_lapce_config = theme_lapce_config;
}
}
let default_lapce_config = DEFAULT_LAPCE_CONFIG.clone();
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.default_color_theme = default_lapce_config.color_theme.clone();
self.default_icon_theme = default_lapce_config.icon_theme.clone();
self.update_id();
}
fn load_color_themes(
disabled_volts: &[VoltID],
extra_plugin_paths: &[PathBuf],
) -> HashMap<String, (String, config::Config)> {
let mut themes = Self::load_local_themes().unwrap_or_default();
for (key, theme) in Self::load_plugin_color_themes(disabled_volts) {
for (key, theme) in
Self::load_plugin_color_themes(disabled_volts, extra_plugin_paths)
{
themes.insert(key, theme);
}
@@ -280,6 +324,10 @@ impl LapceConfig {
themes
}
pub fn default_color_theme(&self) -> &ColorThemeConfig {
&DEFAULT_DARK_THEME_COLOR_CONFIG
}
/// Set the active color theme.
/// Note that this does not save the config.
pub fn set_color_theme(&mut self, workspace: &LapceWorkspace, theme: &str) {
@@ -303,16 +351,19 @@ impl LapceConfig {
/// # Panics
/// If the color was not able to be found in either theme, which may be indicative that
/// it is misspelled or needs to be added to the base-theme.
pub fn get_color(&self, name: &str) -> &Color {
self.color
.ui
.get(name)
.unwrap_or_else(|| panic!("Key not found: {name}"))
pub fn color(&self, name: &str) -> Color {
match self.color.ui.get(name) {
Some(c) => *c,
None => {
error!("Failed to find key: {name}");
Color::HOT_PINK
}
}
}
/// Retrieve a color value whose key starts with "style."
pub fn get_style_color(&self, name: &str) -> Option<&Color> {
self.color.syntax.get(name)
pub fn style_color(&self, name: &str) -> Option<Color> {
self.color.syntax.get(name).copied()
}
pub fn completion_color(
@@ -338,14 +389,14 @@ impl LapceConfig {
_ => "string",
};
self.get_style_color(theme_str).cloned()
self.style_color(theme_str)
}
fn resolve_colors(&mut self, default_config: Option<&LapceConfig>) {
self.color.base = self
.color_theme
.base
.resolve(default_config.map(|c| &c.color.base));
.resolve(default_config.map(|c| &c.color_theme.base));
self.color.ui = self
.color_theme
.resolve_ui_color(&self.color.base, default_config.map(|c| &c.color.ui));
@@ -354,8 +405,8 @@ impl LapceConfig {
default_config.map(|c| &c.color.syntax),
);
let fg = self.get_color(LapceColor::EDITOR_FOREGROUND);
let bg = self.get_color(LapceColor::EDITOR_BACKGROUND);
let fg = self.color(LapceColor::EDITOR_FOREGROUND);
let bg = self.color(LapceColor::EDITOR_BACKGROUND);
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);
@@ -394,6 +445,8 @@ impl LapceConfig {
Some((name.to_lowercase(), (name, config)))
}
/// Load the given theme by its contents.
/// Returns `(name, theme fields)`
fn load_color_theme_from_str(s: &str) -> Option<(String, config::Config)> {
let config = config::Config::builder()
.add_source(config::File::from_str(s, config::FileFormat::Toml))
@@ -406,11 +459,12 @@ impl LapceConfig {
fn load_icon_themes(
disabled_volts: &[VoltID],
extra_plugin_paths: &[PathBuf],
) -> HashMap<String, (String, config::Config, Option<PathBuf>)> {
let mut themes = HashMap::new();
for (key, (name, theme, path)) in
Self::load_plugin_icon_themes(disabled_volts)
Self::load_plugin_icon_themes(disabled_volts, extra_plugin_paths)
{
themes.insert(key, (name, theme, Some(path)));
}
@@ -434,9 +488,10 @@ impl LapceConfig {
fn load_plugin_color_themes(
disabled_volts: &[VoltID],
extra_plugin_paths: &[PathBuf],
) -> HashMap<String, (String, config::Config)> {
let mut themes: HashMap<String, (String, config::Config)> = HashMap::new();
for meta in find_all_volts() {
for meta in find_all_volts(extra_plugin_paths) {
if disabled_volts.contains(&meta.id()) {
continue;
}
@@ -455,10 +510,11 @@ impl LapceConfig {
fn load_plugin_icon_themes(
disabled_volts: &[VoltID],
extra_plugin_paths: &[PathBuf],
) -> HashMap<String, (String, config::Config, PathBuf)> {
let mut themes: HashMap<String, (String, config::Config, PathBuf)> =
HashMap::new();
for meta in find_all_volts() {
for meta in find_all_volts(extra_plugin_paths) {
if disabled_volts.contains(&meta.id()) {
continue;
}
@@ -493,6 +549,19 @@ impl LapceConfig {
))
}
pub fn export_theme(&self) -> String {
let mut table = toml::value::Table::new();
let mut theme = self.color_theme.clone();
theme.name = "".to_string();
table.insert(
"color-theme".to_string(),
toml::Value::try_from(&theme).unwrap(),
);
table.insert("ui".to_string(), toml::Value::try_from(&self.ui).unwrap());
let value = toml::Value::Table(table);
toml::to_string_pretty(&value).unwrap()
}
pub fn settings_file() -> Option<PathBuf> {
let path = Directory::config_directory()?.join("settings.toml");
@@ -526,19 +595,20 @@ impl LapceConfig {
});
svg.unwrap_or_else(|| {
let name = self.default_icon_theme.ui.get(icon).unwrap();
let name = DEFAULT_ICON_THEME_ICON_CONFIG.ui.get(icon).unwrap();
self.svg_store.write().get_default_svg(name)
})
}
pub fn file_svg(&self, path: &Path) -> (String, Option<&Color>) {
pub fn files_svg(&self, paths: &[&Path]) -> (String, Option<Color>) {
let svg = self
.icon_theme
.resolve_path_to_icon(path)
.resolve_path_to_icon(paths)
.and_then(|p| self.svg_store.write().get_svg_on_disk(&p));
if let Some(svg) = svg {
let color = if self.icon_theme.use_editor_color.unwrap_or(false) {
Some(self.get_color(LapceColor::LAPCE_ICON_ACTIVE))
Some(self.color(LapceColor::LAPCE_ICON_ACTIVE))
} else {
None
};
@@ -546,11 +616,15 @@ impl LapceConfig {
} else {
(
self.ui_svg(LapceIcons::FILE),
Some(self.get_color(LapceColor::LAPCE_ICON_ACTIVE)),
Some(self.color(LapceColor::LAPCE_ICON_ACTIVE)),
)
}
}
pub fn file_svg(&self, path: &Path) -> (String, Option<Color>) {
self.files_svg(slice::from_ref(&path))
}
pub fn symbol_svg(&self, kind: &SymbolKind) -> Option<String> {
let kind_str = match *kind {
SymbolKind::ARRAY => LapceIcons::SYMBOL_KIND_ARRAY,
@@ -612,8 +686,17 @@ impl LapceConfig {
}
pub fn terminal_line_height(&self) -> usize {
if self.terminal.line_height > 0 {
self.terminal.line_height
let font_size = self.terminal_font_size();
if self.terminal.line_height > 0.0 {
let line_height = if self.terminal.line_height < SCALE_OR_SIZE_LIMIT {
self.terminal.line_height * font_size as f64
} else {
self.terminal.line_height
};
// Prevent overlapping lines
(line_height.round() as usize).max(font_size)
} else {
self.editor.line_height()
}
@@ -621,37 +704,37 @@ impl LapceConfig {
pub fn terminal_get_color(
&self,
color: &alacritty_terminal::ansi::Color,
color: &alacritty_terminal::vte::ansi::Color,
colors: &alacritty_terminal::term::color::Colors,
) -> Color {
match color {
alacritty_terminal::ansi::Color::Named(color) => {
alacritty_terminal::vte::ansi::Color::Named(color) => {
self.terminal_get_named_color(color)
}
alacritty_terminal::ansi::Color::Spec(rgb) => {
alacritty_terminal::vte::ansi::Color::Spec(rgb) => {
Color::rgb8(rgb.r, rgb.g, rgb.b)
}
alacritty_terminal::ansi::Color::Indexed(index) => {
alacritty_terminal::vte::ansi::Color::Indexed(index) => {
if let Some(rgb) = colors[*index as usize] {
return Color::rgb8(rgb.r, rgb.g, rgb.b);
}
const NAMED_COLORS: [alacritty_terminal::ansi::NamedColor; 16] = [
alacritty_terminal::ansi::NamedColor::Black,
alacritty_terminal::ansi::NamedColor::Red,
alacritty_terminal::ansi::NamedColor::Green,
alacritty_terminal::ansi::NamedColor::Yellow,
alacritty_terminal::ansi::NamedColor::Blue,
alacritty_terminal::ansi::NamedColor::Magenta,
alacritty_terminal::ansi::NamedColor::Cyan,
alacritty_terminal::ansi::NamedColor::White,
alacritty_terminal::ansi::NamedColor::BrightBlack,
alacritty_terminal::ansi::NamedColor::BrightRed,
alacritty_terminal::ansi::NamedColor::BrightGreen,
alacritty_terminal::ansi::NamedColor::BrightYellow,
alacritty_terminal::ansi::NamedColor::BrightBlue,
alacritty_terminal::ansi::NamedColor::BrightMagenta,
alacritty_terminal::ansi::NamedColor::BrightCyan,
alacritty_terminal::ansi::NamedColor::BrightWhite,
const NAMED_COLORS: [alacritty_terminal::vte::ansi::NamedColor; 16] = [
alacritty_terminal::vte::ansi::NamedColor::Black,
alacritty_terminal::vte::ansi::NamedColor::Red,
alacritty_terminal::vte::ansi::NamedColor::Green,
alacritty_terminal::vte::ansi::NamedColor::Yellow,
alacritty_terminal::vte::ansi::NamedColor::Blue,
alacritty_terminal::vte::ansi::NamedColor::Magenta,
alacritty_terminal::vte::ansi::NamedColor::Cyan,
alacritty_terminal::vte::ansi::NamedColor::White,
alacritty_terminal::vte::ansi::NamedColor::BrightBlack,
alacritty_terminal::vte::ansi::NamedColor::BrightRed,
alacritty_terminal::vte::ansi::NamedColor::BrightGreen,
alacritty_terminal::vte::ansi::NamedColor::BrightYellow,
alacritty_terminal::vte::ansi::NamedColor::BrightBlue,
alacritty_terminal::vte::ansi::NamedColor::BrightMagenta,
alacritty_terminal::vte::ansi::NamedColor::BrightCyan,
alacritty_terminal::vte::ansi::NamedColor::BrightWhite,
];
if (*index as usize) < NAMED_COLORS.len() {
self.terminal_get_named_color(&NAMED_COLORS[*index as usize])
@@ -664,98 +747,98 @@ impl LapceConfig {
fn terminal_get_named_color(
&self,
color: &alacritty_terminal::ansi::NamedColor,
color: &alacritty_terminal::vte::ansi::NamedColor,
) -> Color {
let (color, alpha) = match color {
alacritty_terminal::ansi::NamedColor::Cursor => {
alacritty_terminal::vte::ansi::NamedColor::Cursor => {
(LapceColor::TERMINAL_CURSOR, 1.0)
}
alacritty_terminal::ansi::NamedColor::Foreground => {
alacritty_terminal::vte::ansi::NamedColor::Foreground => {
(LapceColor::TERMINAL_FOREGROUND, 1.0)
}
alacritty_terminal::ansi::NamedColor::Background => {
alacritty_terminal::vte::ansi::NamedColor::Background => {
(LapceColor::TERMINAL_BACKGROUND, 1.0)
}
alacritty_terminal::ansi::NamedColor::Blue => {
alacritty_terminal::vte::ansi::NamedColor::Blue => {
(LapceColor::TERMINAL_BLUE, 1.0)
}
alacritty_terminal::ansi::NamedColor::Green => {
alacritty_terminal::vte::ansi::NamedColor::Green => {
(LapceColor::TERMINAL_GREEN, 1.0)
}
alacritty_terminal::ansi::NamedColor::Yellow => {
alacritty_terminal::vte::ansi::NamedColor::Yellow => {
(LapceColor::TERMINAL_YELLOW, 1.0)
}
alacritty_terminal::ansi::NamedColor::Red => {
alacritty_terminal::vte::ansi::NamedColor::Red => {
(LapceColor::TERMINAL_RED, 1.0)
}
alacritty_terminal::ansi::NamedColor::White => {
alacritty_terminal::vte::ansi::NamedColor::White => {
(LapceColor::TERMINAL_WHITE, 1.0)
}
alacritty_terminal::ansi::NamedColor::Black => {
alacritty_terminal::vte::ansi::NamedColor::Black => {
(LapceColor::TERMINAL_BLACK, 1.0)
}
alacritty_terminal::ansi::NamedColor::Cyan => {
alacritty_terminal::vte::ansi::NamedColor::Cyan => {
(LapceColor::TERMINAL_CYAN, 1.0)
}
alacritty_terminal::ansi::NamedColor::Magenta => {
alacritty_terminal::vte::ansi::NamedColor::Magenta => {
(LapceColor::TERMINAL_MAGENTA, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightBlue => {
alacritty_terminal::vte::ansi::NamedColor::BrightBlue => {
(LapceColor::TERMINAL_BRIGHT_BLUE, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightGreen => {
alacritty_terminal::vte::ansi::NamedColor::BrightGreen => {
(LapceColor::TERMINAL_BRIGHT_GREEN, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightYellow => {
alacritty_terminal::vte::ansi::NamedColor::BrightYellow => {
(LapceColor::TERMINAL_BRIGHT_YELLOW, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightRed => {
alacritty_terminal::vte::ansi::NamedColor::BrightRed => {
(LapceColor::TERMINAL_BRIGHT_RED, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightWhite => {
alacritty_terminal::vte::ansi::NamedColor::BrightWhite => {
(LapceColor::TERMINAL_BRIGHT_WHITE, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightBlack => {
alacritty_terminal::vte::ansi::NamedColor::BrightBlack => {
(LapceColor::TERMINAL_BRIGHT_BLACK, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightCyan => {
alacritty_terminal::vte::ansi::NamedColor::BrightCyan => {
(LapceColor::TERMINAL_BRIGHT_CYAN, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightMagenta => {
alacritty_terminal::vte::ansi::NamedColor::BrightMagenta => {
(LapceColor::TERMINAL_BRIGHT_MAGENTA, 1.0)
}
alacritty_terminal::ansi::NamedColor::BrightForeground => {
alacritty_terminal::vte::ansi::NamedColor::BrightForeground => {
(LapceColor::TERMINAL_FOREGROUND, 1.0)
}
alacritty_terminal::ansi::NamedColor::DimBlack => {
alacritty_terminal::vte::ansi::NamedColor::DimBlack => {
(LapceColor::TERMINAL_BLACK, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimRed => {
alacritty_terminal::vte::ansi::NamedColor::DimRed => {
(LapceColor::TERMINAL_RED, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimGreen => {
alacritty_terminal::vte::ansi::NamedColor::DimGreen => {
(LapceColor::TERMINAL_GREEN, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimYellow => {
alacritty_terminal::vte::ansi::NamedColor::DimYellow => {
(LapceColor::TERMINAL_YELLOW, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimBlue => {
alacritty_terminal::vte::ansi::NamedColor::DimBlue => {
(LapceColor::TERMINAL_BLUE, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimMagenta => {
alacritty_terminal::vte::ansi::NamedColor::DimMagenta => {
(LapceColor::TERMINAL_MAGENTA, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimCyan => {
alacritty_terminal::vte::ansi::NamedColor::DimCyan => {
(LapceColor::TERMINAL_CYAN, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimWhite => {
alacritty_terminal::vte::ansi::NamedColor::DimWhite => {
(LapceColor::TERMINAL_WHITE, 0.66)
}
alacritty_terminal::ansi::NamedColor::DimForeground => {
alacritty_terminal::vte::ansi::NamedColor::DimForeground => {
(LapceColor::TERMINAL_FOREGROUND, 0.66)
}
};
(*self.get_color(color)).with_alpha_factor(alpha)
self.color(color).with_alpha_factor(alpha)
}
/// Get the dropdown information for the specific setting, used for the settings UI.
@@ -779,6 +862,42 @@ impl LapceConfig {
.unwrap_or(0),
items: self.icon_theme_list.clone(),
}),
("editor", "wrap-style") => Some(DropdownInfo {
// TODO: it would be better to have the text not be the default kebab-case when
// displayed in settings, but we would need to map back from the dropdown's value
// or index.
active_index: self
.wrap_style_list
.iter()
.flat_map(|w| WrapStyle::try_from_str(w))
.position(|w| w == self.editor.wrap_style)
.unwrap_or(0),
items: self.wrap_style_list.clone(),
}),
("ui", "tab-close-button") => Some(DropdownInfo {
active_index: self.ui.tab_close_button as usize,
items: ui::TabCloseButton::VARIANTS
.iter()
.map(|s| s.to_string())
.sorted()
.collect(),
}),
("terminal", "default-profile") => Some(DropdownInfo {
active_index: self
.terminal
.profiles
.iter()
.position(|(profile_name, _)| {
profile_name
== self
.terminal
.default_profile
.get(std::env::consts::OS)
.unwrap_or(&String::from("default"))
})
.unwrap_or(0),
items: self.terminal.profiles.clone().into_keys().collect(),
}),
_ => None,
}
}
@@ -790,6 +909,30 @@ impl LapceConfig {
Some(document)
}
pub fn reset_setting(parent: &str, key: &str) -> Option<()> {
let mut main_table = Self::get_file_table().unwrap_or_default();
// Find the container table
let mut table = main_table.as_table_mut();
for key in parent.split('.') {
if !table.contains_key(key) {
table.insert(
key,
toml_edit::Item::Table(toml_edit::Table::default()),
);
}
table = table.get_mut(key)?.as_table_mut()?;
}
table.remove(key);
// Store
let path = Self::settings_file()?;
std::fs::write(path, main_table.to_string().as_bytes()).ok()?;
Some(())
}
/// Update the config file with the given edit.
/// This should be called whenever the configuration is changed, so that it is persisted.
pub fn update_file(
@@ -797,6 +940,16 @@ impl LapceConfig {
key: &str,
value: toml_edit::Value,
) -> Option<()> {
// TODO: This is a hack to fix the fact that terminal default profile is saved in a
// different manner than other fields. As it is per-operating-system.
// Thus we have to instead set the terminal.default-profile.{OS}
// It would be better to not need a special hack.
let (parent, key) = if parent == "terminal" && key == "default-profile" {
("terminal.default-profile", std::env::consts::OS)
} else {
(parent, key)
};
let mut main_table = Self::get_file_table().unwrap_or_default();
// Find the container table
+125 -99
View File
@@ -11,6 +11,10 @@ pub enum LoadThemeError {
themes_folder: PathBuf,
theme_name: String,
},
#[error("recursion limit reached for {variable_name}")]
RecursionLimitReached { variable_name: String },
#[error("variable {variable_name} not found")]
VariableNotFound { variable_name: String },
#[error("There was an error reading the theme file")]
Read(std::io::Error),
}
@@ -18,138 +22,160 @@ pub enum LoadThemeError {
pub struct LapceColor {}
impl LapceColor {
pub const LAPCE_WARN: &str = "lapce.warn";
pub const LAPCE_ERROR: &str = "lapce.error";
pub const LAPCE_DROPDOWN_SHADOW: &str = "lapce.dropdown_shadow";
pub const LAPCE_BORDER: &str = "lapce.border";
pub const LAPCE_SCROLL_BAR: &str = "lapce.scroll_bar";
pub const LAPCE_WARN: &'static str = "lapce.warn";
pub const LAPCE_ERROR: &'static str = "lapce.error";
pub const LAPCE_DROPDOWN_SHADOW: &'static str = "lapce.dropdown_shadow";
pub const LAPCE_BORDER: &'static str = "lapce.border";
pub const LAPCE_SCROLL_BAR: &'static str = "lapce.scroll_bar";
pub const LAPCE_BUTTON_PRIMARY_BACKGROUND: &str =
pub const LAPCE_BUTTON_PRIMARY_BACKGROUND: &'static str =
"lapce.button.primary.background";
pub const LAPCE_BUTTON_PRIMARY_FOREGROUND: &str =
pub const LAPCE_BUTTON_PRIMARY_FOREGROUND: &'static str =
"lapce.button.primary.foreground";
pub const LAPCE_TAB_ACTIVE_BACKGROUND: &str = "lapce.tab.active.background";
pub const LAPCE_TAB_ACTIVE_FOREGROUND: &str = "lapce.tab.active.foreground";
pub const LAPCE_TAB_ACTIVE_UNDERLINE: &str = "lapce.tab.active.underline";
pub const LAPCE_TAB_ACTIVE_BACKGROUND: &'static str =
"lapce.tab.active.background";
pub const LAPCE_TAB_ACTIVE_FOREGROUND: &'static str =
"lapce.tab.active.foreground";
pub const LAPCE_TAB_ACTIVE_UNDERLINE: &'static str =
"lapce.tab.active.underline";
pub const LAPCE_TAB_INACTIVE_BACKGROUND: &str = "lapce.tab.inactive.background";
pub const LAPCE_TAB_INACTIVE_FOREGROUND: &str = "lapce.tab.inactive.foreground";
pub const LAPCE_TAB_INACTIVE_UNDERLINE: &str = "lapce.tab.inactive.underline";
pub const LAPCE_TAB_INACTIVE_BACKGROUND: &'static str =
"lapce.tab.inactive.background";
pub const LAPCE_TAB_INACTIVE_FOREGROUND: &'static str =
"lapce.tab.inactive.foreground";
pub const LAPCE_TAB_INACTIVE_UNDERLINE: &'static str =
"lapce.tab.inactive.underline";
pub const LAPCE_TAB_SEPARATOR: &str = "lapce.tab.separator";
pub const LAPCE_TAB_SEPARATOR: &'static str = "lapce.tab.separator";
pub const LAPCE_ICON_ACTIVE: &str = "lapce.icon.active";
pub const LAPCE_ICON_INACTIVE: &str = "lapce.icon.inactive";
pub const LAPCE_ICON_ACTIVE: &'static str = "lapce.icon.active";
pub const LAPCE_ICON_INACTIVE: &'static str = "lapce.icon.inactive";
pub const LAPCE_REMOTE_ICON: &str = "lapce.remote.icon";
pub const LAPCE_REMOTE_LOCAL: &str = "lapce.remote.local";
pub const LAPCE_REMOTE_CONNECTED: &str = "lapce.remote.connected";
pub const LAPCE_REMOTE_CONNECTING: &str = "lapce.remote.connecting";
pub const LAPCE_REMOTE_DISCONNECTED: &str = "lapce.remote.disconnected";
pub const LAPCE_REMOTE_ICON: &'static str = "lapce.remote.icon";
pub const LAPCE_REMOTE_LOCAL: &'static str = "lapce.remote.local";
pub const LAPCE_REMOTE_CONNECTED: &'static str = "lapce.remote.connected";
pub const LAPCE_REMOTE_CONNECTING: &'static str = "lapce.remote.connecting";
pub const LAPCE_REMOTE_DISCONNECTED: &'static str = "lapce.remote.disconnected";
pub const LAPCE_PLUGIN_NAME: &str = "lapce.plugin.name";
pub const LAPCE_PLUGIN_DESCRIPTION: &str = "lapce.plugin.description";
pub const LAPCE_PLUGIN_AUTHOR: &str = "lapce.plugin.author";
pub const LAPCE_PLUGIN_NAME: &'static str = "lapce.plugin.name";
pub const LAPCE_PLUGIN_DESCRIPTION: &'static str = "lapce.plugin.description";
pub const LAPCE_PLUGIN_AUTHOR: &'static str = "lapce.plugin.author";
pub const EDITOR_BACKGROUND: &str = "editor.background";
pub const EDITOR_FOREGROUND: &str = "editor.foreground";
pub const EDITOR_DIM: &str = "editor.dim";
pub const EDITOR_FOCUS: &str = "editor.focus";
pub const EDITOR_CARET: &str = "editor.caret";
pub const EDITOR_SELECTION: &str = "editor.selection";
pub const EDITOR_CURRENT_LINE: &str = "editor.current_line";
pub const EDITOR_LINK: &str = "editor.link";
pub const EDITOR_VISIBLE_WHITESPACE: &str = "editor.visible_whitespace";
pub const EDITOR_INDENT_GUIDE: &str = "editor.indent_guide";
pub const EDITOR_DRAG_DROP_BACKGROUND: &str = "editor.drag_drop_background";
pub const EDITOR_STICKY_HEADER_BACKGROUND: &str =
pub const EDITOR_BACKGROUND: &'static str = "editor.background";
pub const EDITOR_FOREGROUND: &'static str = "editor.foreground";
pub const EDITOR_DIM: &'static str = "editor.dim";
pub const EDITOR_FOCUS: &'static str = "editor.focus";
pub const EDITOR_CARET: &'static str = "editor.caret";
pub const EDITOR_SELECTION: &'static str = "editor.selection";
pub const EDITOR_DEBUG_BREAK_LINE: &'static str = "editor.debug_break_line";
pub const EDITOR_CURRENT_LINE: &'static str = "editor.current_line";
pub const EDITOR_LINK: &'static str = "editor.link";
pub const EDITOR_VISIBLE_WHITESPACE: &'static str = "editor.visible_whitespace";
pub const EDITOR_INDENT_GUIDE: &'static str = "editor.indent_guide";
pub const EDITOR_DRAG_DROP_BACKGROUND: &'static str =
"editor.drag_drop_background";
pub const EDITOR_STICKY_HEADER_BACKGROUND: &'static str =
"editor.sticky_header_background";
pub const EDITOR_DRAG_DROP_TAB_BACKGROUND: &str =
pub const EDITOR_DRAG_DROP_TAB_BACKGROUND: &'static str =
"editor.drag_drop_tab_background";
pub const INLAY_HINT_FOREGROUND: &str = "inlay_hint.foreground";
pub const INLAY_HINT_BACKGROUND: &str = "inlay_hint.background";
pub const INLAY_HINT_FOREGROUND: &'static str = "inlay_hint.foreground";
pub const INLAY_HINT_BACKGROUND: &'static str = "inlay_hint.background";
pub const ERROR_LENS_ERROR_FOREGROUND: &str = "error_lens.error.foreground";
pub const ERROR_LENS_ERROR_BACKGROUND: &str = "error_lens.error.background";
pub const ERROR_LENS_WARNING_FOREGROUND: &str = "error_lens.warning.foreground";
pub const ERROR_LENS_WARNING_BACKGROUND: &str = "error_lens.warning.background";
pub const ERROR_LENS_OTHER_FOREGROUND: &str = "error_lens.other.foreground";
pub const ERROR_LENS_OTHER_BACKGROUND: &str = "error_lens.other.background";
pub const ERROR_LENS_ERROR_FOREGROUND: &'static str =
"error_lens.error.foreground";
pub const ERROR_LENS_ERROR_BACKGROUND: &'static str =
"error_lens.error.background";
pub const ERROR_LENS_WARNING_FOREGROUND: &'static str =
"error_lens.warning.foreground";
pub const ERROR_LENS_WARNING_BACKGROUND: &'static str =
"error_lens.warning.background";
pub const ERROR_LENS_OTHER_FOREGROUND: &'static str =
"error_lens.other.foreground";
pub const ERROR_LENS_OTHER_BACKGROUND: &'static str =
"error_lens.other.background";
pub const COMPLETION_LENS_FOREGROUND: &str = "completion_lens.foreground";
pub const COMPLETION_LENS_FOREGROUND: &'static str =
"completion_lens.foreground";
pub const SOURCE_CONTROL_ADDED: &str = "source_control.added";
pub const SOURCE_CONTROL_REMOVED: &str = "source_control.removed";
pub const SOURCE_CONTROL_MODIFIED: &str = "source_control.modified";
pub const SOURCE_CONTROL_ADDED: &'static str = "source_control.added";
pub const SOURCE_CONTROL_REMOVED: &'static str = "source_control.removed";
pub const SOURCE_CONTROL_MODIFIED: &'static str = "source_control.modified";
pub const TERMINAL_CURSOR: &str = "terminal.cursor";
pub const TERMINAL_BACKGROUND: &str = "terminal.background";
pub const TERMINAL_FOREGROUND: &str = "terminal.foreground";
pub const TERMINAL_RED: &str = "terminal.red";
pub const TERMINAL_BLUE: &str = "terminal.blue";
pub const TERMINAL_GREEN: &str = "terminal.green";
pub const TERMINAL_YELLOW: &str = "terminal.yellow";
pub const TERMINAL_BLACK: &str = "terminal.black";
pub const TERMINAL_WHITE: &str = "terminal.white";
pub const TERMINAL_CYAN: &str = "terminal.cyan";
pub const TERMINAL_MAGENTA: &str = "terminal.magenta";
pub const TERMINAL_CURSOR: &'static str = "terminal.cursor";
pub const TERMINAL_BACKGROUND: &'static str = "terminal.background";
pub const TERMINAL_FOREGROUND: &'static str = "terminal.foreground";
pub const TERMINAL_RED: &'static str = "terminal.red";
pub const TERMINAL_BLUE: &'static str = "terminal.blue";
pub const TERMINAL_GREEN: &'static str = "terminal.green";
pub const TERMINAL_YELLOW: &'static str = "terminal.yellow";
pub const TERMINAL_BLACK: &'static str = "terminal.black";
pub const TERMINAL_WHITE: &'static str = "terminal.white";
pub const TERMINAL_CYAN: &'static str = "terminal.cyan";
pub const TERMINAL_MAGENTA: &'static str = "terminal.magenta";
pub const TERMINAL_BRIGHT_RED: &str = "terminal.bright_red";
pub const TERMINAL_BRIGHT_BLUE: &str = "terminal.bright_blue";
pub const TERMINAL_BRIGHT_GREEN: &str = "terminal.bright_green";
pub const TERMINAL_BRIGHT_YELLOW: &str = "terminal.bright_yellow";
pub const TERMINAL_BRIGHT_BLACK: &str = "terminal.bright_black";
pub const TERMINAL_BRIGHT_WHITE: &str = "terminal.bright_white";
pub const TERMINAL_BRIGHT_CYAN: &str = "terminal.bright_cyan";
pub const TERMINAL_BRIGHT_MAGENTA: &str = "terminal.bright_magenta";
pub const TERMINAL_BRIGHT_RED: &'static str = "terminal.bright_red";
pub const TERMINAL_BRIGHT_BLUE: &'static str = "terminal.bright_blue";
pub const TERMINAL_BRIGHT_GREEN: &'static str = "terminal.bright_green";
pub const TERMINAL_BRIGHT_YELLOW: &'static str = "terminal.bright_yellow";
pub const TERMINAL_BRIGHT_BLACK: &'static str = "terminal.bright_black";
pub const TERMINAL_BRIGHT_WHITE: &'static str = "terminal.bright_white";
pub const TERMINAL_BRIGHT_CYAN: &'static str = "terminal.bright_cyan";
pub const TERMINAL_BRIGHT_MAGENTA: &'static str = "terminal.bright_magenta";
pub const PALETTE_BACKGROUND: &str = "palette.background";
pub const PALETTE_FOREGROUND: &str = "palette.foreground";
pub const PALETTE_CURRENT_BACKGROUND: &str = "palette.current.background";
pub const PALETTE_CURRENT_FOREGROUND: &str = "palette.current.foreground";
pub const PALETTE_BACKGROUND: &'static str = "palette.background";
pub const PALETTE_FOREGROUND: &'static str = "palette.foreground";
pub const PALETTE_CURRENT_BACKGROUND: &'static str =
"palette.current.background";
pub const PALETTE_CURRENT_FOREGROUND: &'static str =
"palette.current.foreground";
pub const COMPLETION_BACKGROUND: &str = "completion.background";
pub const COMPLETION_CURRENT: &str = "completion.current";
pub const COMPLETION_BACKGROUND: &'static str = "completion.background";
pub const COMPLETION_CURRENT: &'static str = "completion.current";
pub const HOVER_BACKGROUND: &str = "hover.background";
pub const HOVER_BACKGROUND: &'static str = "hover.background";
pub const ACTIVITY_BACKGROUND: &str = "activity.background";
pub const ACTIVITY_CURRENT: &str = "activity.current";
pub const ACTIVITY_BACKGROUND: &'static str = "activity.background";
pub const ACTIVITY_CURRENT: &'static str = "activity.current";
pub const DEBUG_BREAKPOINT: &str = "debug.breakpoint";
pub const DEBUG_BREAKPOINT_HOVER: &str = "debug.breakpoint.hover";
pub const DEBUG_BREAKPOINT: &'static str = "debug.breakpoint";
pub const DEBUG_BREAKPOINT_HOVER: &'static str = "debug.breakpoint.hover";
pub const PANEL_BACKGROUND: &str = "panel.background";
pub const PANEL_FOREGROUND: &str = "panel.foreground";
pub const PANEL_FOREGROUND_DIM: &str = "panel.foreground.dim";
pub const PANEL_CURRENT_BACKGROUND: &str = "panel.current.background";
pub const PANEL_CURRENT_FOREGROUND: &str = "panel.current.foreground";
pub const PANEL_CURRENT_FOREGROUND_DIM: &str = "panel.current.foreground.dim";
pub const PANEL_HOVERED_BACKGROUND: &str = "panel.hovered.background";
pub const PANEL_HOVERED_ACTIVE_BACKGROUND: &str =
pub const TOOLTIP_BACKGROUND: &'static str = "tooltip.background";
pub const TOOLTIP_FOREGROUND: &'static str = "tooltip.foreground";
pub const PANEL_BACKGROUND: &'static str = "panel.background";
pub const PANEL_FOREGROUND: &'static str = "panel.foreground";
pub const PANEL_FOREGROUND_DIM: &'static str = "panel.foreground.dim";
pub const PANEL_CURRENT_BACKGROUND: &'static str = "panel.current.background";
pub const PANEL_CURRENT_FOREGROUND: &'static str = "panel.current.foreground";
pub const PANEL_CURRENT_FOREGROUND_DIM: &'static str =
"panel.current.foreground.dim";
pub const PANEL_HOVERED_BACKGROUND: &'static str = "panel.hovered.background";
pub const PANEL_HOVERED_ACTIVE_BACKGROUND: &'static str =
"panel.hovered.active.background";
pub const PANEL_HOVERED_FOREGROUND: &str = "panel.hovered.foreground";
pub const PANEL_HOVERED_FOREGROUND_DIM: &str = "panel.hovered.foreground.dim";
pub const PANEL_HOVERED_FOREGROUND: &'static str = "panel.hovered.foreground";
pub const PANEL_HOVERED_FOREGROUND_DIM: &'static str =
"panel.hovered.foreground.dim";
pub const STATUS_BACKGROUND: &str = "status.background";
pub const STATUS_FOREGROUND: &str = "status.foreground";
pub const STATUS_MODAL_NORMAL_BACKGROUND: &str =
pub const STATUS_BACKGROUND: &'static str = "status.background";
pub const STATUS_FOREGROUND: &'static str = "status.foreground";
pub const STATUS_MODAL_NORMAL_BACKGROUND: &'static str =
"status.modal.normal.background";
pub const STATUS_MODAL_NORMAL_FOREGROUND: &str =
pub const STATUS_MODAL_NORMAL_FOREGROUND: &'static str =
"status.modal.normal.foreground";
pub const STATUS_MODAL_INSERT_BACKGROUND: &str =
pub const STATUS_MODAL_INSERT_BACKGROUND: &'static str =
"status.modal.insert.background";
pub const STATUS_MODAL_INSERT_FOREGROUND: &str =
pub const STATUS_MODAL_INSERT_FOREGROUND: &'static str =
"status.modal.insert.foreground";
pub const STATUS_MODAL_VISUAL_BACKGROUND: &str =
pub const STATUS_MODAL_VISUAL_BACKGROUND: &'static str =
"status.modal.visual.background";
pub const STATUS_MODAL_VISUAL_FOREGROUND: &str =
pub const STATUS_MODAL_VISUAL_FOREGROUND: &'static str =
"status.modal.visual.foreground";
pub const STATUS_MODAL_TERMINAL_BACKGROUND: &str =
pub const STATUS_MODAL_TERMINAL_BACKGROUND: &'static str =
"status.modal.terminal.background";
pub const STATUS_MODAL_TERMINAL_FOREGROUND: &str =
pub const STATUS_MODAL_TERMINAL_FOREGROUND: &'static str =
"status.modal.terminal.foreground";
pub const MARKDOWN_BLOCKQUOTE: &'static str = "markdown.blockquote";
+177 -102
View File
@@ -1,9 +1,13 @@
use std::{collections::HashMap, path::PathBuf};
use std::{
collections::{BTreeMap, HashMap},
path::PathBuf,
};
use floem::peniko::Color;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use super::color::LoadThemeError;
#[derive(Debug, Clone, Default)]
pub enum ThemeColorPreference {
#[default]
@@ -13,65 +17,17 @@ pub enum ThemeColorPreference {
HighContrastLight,
}
#[derive(Debug, Clone)]
pub struct ThemeBaseColor {
pub white: Color,
pub black: Color,
pub grey: Color,
pub blue: Color,
pub red: Color,
pub yellow: Color,
pub orange: Color,
pub green: Color,
pub purple: Color,
pub cyan: Color,
pub magenta: Color,
}
impl Default for ThemeBaseColor {
fn default() -> Self {
Self {
white: Color::rgb8(0, 0, 0),
black: Color::rgb8(0, 0, 0),
grey: Color::rgb8(0, 0, 0),
blue: Color::rgb8(0, 0, 0),
red: Color::rgb8(0, 0, 0),
yellow: Color::rgb8(0, 0, 0),
orange: Color::rgb8(0, 0, 0),
green: Color::rgb8(0, 0, 0),
purple: Color::rgb8(0, 0, 0),
cyan: Color::rgb8(0, 0, 0),
magenta: Color::rgb8(0, 0, 0),
}
}
}
/// Holds all the resolved theme variables
#[derive(Debug, Clone, Default)]
pub struct ThemeBaseColor(HashMap<String, Color>);
impl ThemeBaseColor {
pub fn get(&self, name: &str) -> Option<&Color> {
Some(match name {
"white" => &self.white,
"black" => &self.black,
"grey" => &self.grey,
"blue" => &self.blue,
"red" => &self.red,
"yellow" => &self.yellow,
"orange" => &self.orange,
"green" => &self.green,
"purple" => &self.purple,
"cyan" => &self.cyan,
"magenta" => &self.magenta,
_ => return None,
})
}
pub fn keys(&self) -> [&'static str; 11] {
[
"white", "black", "grey", "blue", "red", "yellow", "orange", "green",
"purple", "cyan", "magenta",
]
pub fn get(&self, name: &str) -> Option<Color> {
self.0.get(name).map(ToOwned::to_owned)
}
}
pub const THEME_RECURSION_LIMIT: usize = 6;
#[derive(Debug, Clone, Default)]
pub struct ThemeColor {
pub color_preference: ThemeColorPreference,
@@ -81,71 +37,108 @@ pub struct ThemeColor {
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct ThemeBaseConfig {
pub white: String,
pub black: String,
pub grey: String,
pub blue: String,
pub red: String,
pub yellow: String,
pub orange: String,
pub green: String,
pub purple: String,
pub cyan: String,
pub magenta: String,
}
pub struct ThemeBaseConfig(BTreeMap<String, String>);
impl ThemeBaseConfig {
pub fn resolve(&self, default: Option<&ThemeBaseColor>) -> ThemeBaseColor {
/// Resolve the variables in this theme base config into the actual colors.
/// The basic idea is just: `"field`: some value` does:
/// - If the value does not start with `$`, then it is a color and we return it
/// - If the value starts with `$` then it is a variable
/// - Look it up in the current theme
/// - If not found, look it up in the default theme
/// - If not found, return `Color::HOT_PINK` as a fallback
///
/// Note that this applies even if the default theme colors have a variable.
/// This allows the default theme to have, for example, a `$uibg` variable that the current
/// them can override so that if there's ever a new ui element using that variable, the theme
/// does not have to be updated.
pub fn resolve(&self, default: Option<&ThemeBaseConfig>) -> ThemeBaseColor {
let default = default.cloned().unwrap_or_default();
ThemeBaseColor {
white: Color::parse(&self.white).unwrap_or(default.white),
black: Color::parse(&self.black).unwrap_or(default.black),
grey: Color::parse(&self.grey).unwrap_or(default.grey),
blue: Color::parse(&self.blue).unwrap_or(default.blue),
red: Color::parse(&self.red).unwrap_or(default.red),
yellow: Color::parse(&self.yellow).unwrap_or(default.yellow),
orange: Color::parse(&self.orange).unwrap_or(default.orange),
green: Color::parse(&self.green).unwrap_or(default.green),
purple: Color::parse(&self.purple).unwrap_or(default.purple),
cyan: Color::parse(&self.cyan).unwrap_or(default.cyan),
magenta: Color::parse(&self.magenta).unwrap_or(default.magenta),
let mut base = ThemeBaseColor(HashMap::new());
// We resolve all the variables to their values
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(|| {
tracing::warn!(
"Failed to parse color theme variable for ({key}: {value})"
);
Color::HOT_PINK
});
base.0.insert(key.to_string(), color);
}
Ok(None) => {
tracing::warn!(
"Failed to resolve color theme variable for ({key}: {value})"
);
}
Err(err) => {
tracing::error!(
"Failed to resolve color theme variable ({key}: {value}): {err}"
);
}
}
}
base
}
fn resolve_variable<'a>(
&'a self,
defaults: &'a ThemeBaseConfig,
key: &str,
value: &'a str,
i: usize,
) -> Result<Option<&'a str>, LoadThemeError> {
let Some(value) = value.strip_prefix('$') else {
return Ok(Some(value));
};
if i > THEME_RECURSION_LIMIT {
return Err(LoadThemeError::RecursionLimitReached {
variable_name: key.to_string(),
});
}
let target =
self.get(value)
.or_else(|| defaults.get(value))
.ok_or_else(|| LoadThemeError::VariableNotFound {
variable_name: key.to_string(),
})?;
self.resolve_variable(defaults, value, target, i + 1)
}
// Note: this returns an `&String` just to make it consistent with hashmap lookups that are
// also used via ui/syntax
pub fn get(&self, name: &str) -> Option<&String> {
Some(match name {
"white" => &self.white,
"black" => &self.black,
"grey" => &self.grey,
"blue" => &self.blue,
"red" => &self.red,
"yellow" => &self.yellow,
"orange" => &self.orange,
"green" => &self.green,
"purple" => &self.purple,
"cyan" => &self.cyan,
"magenta" => &self.magenta,
_ => return None,
})
self.0.get(name)
}
pub fn key_values(&self) -> BTreeMap<String, String> {
self.0.clone()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
#[serde(rename_all = "kebab-case", default)]
pub struct ColorThemeConfig {
#[serde(skip)]
pub path: PathBuf,
pub name: String,
pub high_contrast: Option<bool>,
pub base: ThemeBaseConfig,
pub syntax: IndexMap<String, String>,
pub ui: IndexMap<String, String>,
pub syntax: BTreeMap<String, String>,
pub ui: BTreeMap<String, String>,
}
impl ColorThemeConfig {
fn resolve_color(
colors: &IndexMap<String, String>,
colors: &BTreeMap<String, String>,
base: &ThemeBaseColor,
default: Option<&HashMap<String, Color>>,
) -> HashMap<String, Color> {
@@ -153,7 +146,7 @@ impl ColorThemeConfig {
.iter()
.map(|(name, hex)| {
let color = if let Some(stripped) = hex.strip_prefix('$') {
base.get(stripped).cloned()
base.get(stripped)
} else {
Color::parse(hex)
};
@@ -185,3 +178,85 @@ impl ColorThemeConfig {
Self::resolve_color(&self.syntax, base, default)
}
}
#[cfg(test)]
mod tests {
use config::Config;
use floem::peniko::Color;
use crate::{config::LapceConfig, workspace::LapceWorkspace};
#[test]
fn test_resolve() {
// Mimicking load
let workspace = LapceWorkspace::default();
let config = LapceConfig::merge_config(&workspace, None, None);
let mut lapce_config: LapceConfig = config.try_deserialize().unwrap();
let test_theme_str = r##"
[color-theme]
name = "test"
color-preference = "dark"
[ui]
[color-theme.base]
"blah" = "#ff00ff"
"text" = "#000000"
[color-theme.syntax]
[color-theme.ui]
"lapce.error" = "#ffffff"
"editor.background" = "$blah"
"##;
println!("Test theme: {test_theme_str}");
let test_theme_cfg = Config::builder()
.add_source(config::File::from_str(
test_theme_str,
config::FileFormat::Toml,
))
.build()
.unwrap();
lapce_config.available_color_themes =
[("test".to_string(), ("test".to_string(), test_theme_cfg))]
.into_iter()
.collect();
// lapce_config.available_icon_themes = Some(vec![]);
lapce_config.core.color_theme = "test".to_string();
lapce_config.resolve_theme(&workspace);
println!("Hot Pink: {:?}", Color::HOT_PINK);
// test basic override
assert_eq!(
lapce_config.color("lapce.error"),
Color::WHITE,
"Failed to get basic theme override"
);
// test that it falls through to the dark theme for unspecified color
assert_eq!(
lapce_config.color("lapce.warn"),
Color::rgb8(0xE5, 0xC0, 0x7B),
"Failed to get from fallback dark theme"
); // $yellow
// test that our custom variable worked
assert_eq!(
lapce_config.color("editor.background"),
Color::rgb8(0xFF, 0x00, 0xFF),
"Failed to get from custom variable"
);
// test that for text it now uses our redeclared variable
assert_eq!(
lapce_config.color("editor.foreground"),
Color::BLACK,
"Failed to get from custom variable circle back around"
);
// don't bother filling color/icon theme list
// don't bother with wrap style list
// don't bother with terminal colors
}
}
+90 -5
View File
@@ -1,6 +1,9 @@
use floem::views::editor::text::RenderWhitespace;
use serde::{Deserialize, Serialize};
use structdesc::FieldNames;
pub const SCALE_OR_SIZE_LIMIT: f64 = 5.0;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum ClickMode {
#[default]
@@ -12,6 +15,48 @@ pub enum ClickMode {
DoubleClickAll,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum WrapStyle {
/// No wrapping
None,
/// Wrap at the editor width
#[default]
EditorWidth,
// /// Wrap at the wrap-column
// WrapColumn,
/// Wrap at a specific width
WrapWidth,
}
impl WrapStyle {
pub fn as_str(&self) -> &'static str {
match self {
WrapStyle::None => "none",
WrapStyle::EditorWidth => "editor-width",
// WrapStyle::WrapColumn => "wrap-column",
WrapStyle::WrapWidth => "wrap-width",
}
}
pub fn try_from_str(s: &str) -> Option<Self> {
match s {
"none" => Some(WrapStyle::None),
"editor-width" => Some(WrapStyle::EditorWidth),
// "wrap-column" => Some(WrapStyle::WrapColumn),
"wrap-width" => Some(WrapStyle::WrapWidth),
_ => None,
}
}
}
impl std::fmt::Display for WrapStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())?;
Ok(())
}
}
#[derive(FieldNames, Debug, Clone, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct EditorConfig {
@@ -25,6 +70,10 @@ pub struct EditorConfig {
desc = "Set the editor line height. If less than 5.0, line height will be a multiple of the font size."
)]
line_height: f64,
#[field_names(
desc = "If enabled, when you input a tab character, it will insert indent that's detected based on your files."
)]
pub smart_tab: bool,
#[field_names(desc = "Set the tab width")]
pub tab_width: usize,
#[field_names(desc = "If opened editors are shown in a tab")]
@@ -37,6 +86,12 @@ pub struct EditorConfig {
desc = "Set the minimum number of visible lines above and below the cursor"
)]
pub cursor_surrounding_lines: usize,
#[field_names(desc = "The kind of wrapping to perform")]
pub wrap_style: WrapStyle,
// #[field_names(desc = "The number of columns to wrap at")]
// pub wrap_column: usize,
#[field_names(desc = "The number of pixels to wrap at")]
pub wrap_width: usize,
#[field_names(
desc = "Show code context like functions and classes at the top of editor when scroll"
)]
@@ -54,9 +109,13 @@ pub struct EditorConfig {
)]
pub signature_label_code_block: bool,
#[field_names(
desc = "Whether the editor should disable automatic closing of matching pairs"
desc = "Whether the editor should enable automatic closing of matching pairs"
)]
pub auto_closing_matching_pairs: bool,
#[field_names(
desc = "Whether the editor should automatically surround selected text when typing quotes or brackets"
)]
pub auto_surround: bool,
#[field_names(
desc = "How long (in ms) it should take before the hover information appears"
)]
@@ -70,6 +129,11 @@ pub struct EditorConfig {
)]
pub format_on_save: bool,
#[field_names(
desc = "Whether newlines should be automatically converted to the current line ending"
)]
pub normalize_line_endings: bool,
#[field_names(desc = "If matching brackets are highlighted")]
pub highlight_matching_brackets: bool,
@@ -93,6 +157,12 @@ pub struct EditorConfig {
desc = "Whether error lens should go to the end of view line, or only to the end of the diagnostic"
)]
pub error_lens_end_of_line: bool,
#[field_names(
desc = "Whether error lens should extend over multiple lines. If false, it will have newlines stripped."
)]
pub error_lens_multiline: bool,
// TODO: Error lens but put entirely on the next line
// TODO: error lens with indentation matching.
#[field_names(
desc = "Set error lens font family. If empty, it uses the inlay hint font family."
)]
@@ -105,6 +175,8 @@ pub struct EditorConfig {
desc = "If the editor should display the completion item as phantom text"
)]
pub enable_completion_lens: bool,
#[field_names(desc = "If the editor should display inline completions")]
pub enable_inline_completion: bool,
#[field_names(
desc = "Set completion lens font family. If empty, it uses the inlay hint font family."
)]
@@ -116,7 +188,7 @@ pub struct EditorConfig {
#[field_names(
desc = "Set the cursor blink interval (in milliseconds). Set to 0 to completely disable."
)]
pub blink_interval: u64, // TODO: change to u128 when upgrading config-rs to >0.11
blink_interval: u64,
#[field_names(
desc = "Whether the multiple cursor selection is case sensitive."
)]
@@ -128,7 +200,7 @@ pub struct EditorConfig {
#[field_names(
desc = "How the editor should render whitespace characters.\nOptions: none, all, boundary, trailing."
)]
pub render_whitespace: String,
pub render_whitespace: RenderWhitespace,
#[field_names(desc = "Whether the editor show indent guide.")]
pub show_indent_guide: bool,
#[field_names(
@@ -153,6 +225,14 @@ pub struct EditorConfig {
desc = "Set the default number of visible lines above and below the diff block (-1 for infinite)"
)]
pub diff_context_lines: i32,
#[field_names(desc = "Whether the editor colorizes brackets")]
pub bracket_pair_colorization: bool,
#[field_names(desc = "Bracket colorization Limit")]
pub bracket_colorization_limit: u64,
#[field_names(
desc = "Glob patterns for excluding files and folders (in file explorer)"
)]
pub files_exclude: String,
}
impl EditorConfig {
@@ -161,8 +241,6 @@ impl EditorConfig {
}
pub fn line_height(&self) -> usize {
const SCALE_OR_SIZE_LIMIT: f64 = 5.0;
let line_height = if self.line_height < SCALE_OR_SIZE_LIMIT {
self.line_height * self.font_size as f64
} else {
@@ -207,4 +285,11 @@ impl EditorConfig {
None
}
}
pub fn blink_interval(&self) -> u64 {
if self.blink_interval == 0 {
return 0;
}
self.blink_interval.max(200)
}
}
+130 -108
View File
@@ -1,85 +1,94 @@
pub struct LapceIcons {}
impl LapceIcons {
pub const WINDOW_CLOSE: &str = "window.close";
pub const WINDOW_RESTORE: &str = "window.restore";
pub const WINDOW_MAXIMIZE: &str = "window.maximize";
pub const WINDOW_MINIMIZE: &str = "window.minimize";
pub const WINDOW_CLOSE: &'static str = "window.close";
pub const WINDOW_RESTORE: &'static str = "window.restore";
pub const WINDOW_MAXIMIZE: &'static str = "window.maximize";
pub const WINDOW_MINIMIZE: &'static str = "window.minimize";
pub const LINK: &str = "link";
pub const ERROR: &str = "error";
pub const ADD: &str = "add";
pub const CLOSE: &str = "close";
pub const REMOTE: &str = "remote";
pub const PROBLEM: &str = "error";
pub const DEBUG: &str = "debug";
pub const DEBUG_ALT: &str = "debug_alt";
pub const DEBUG_BREAKPOINT: &str = "debug_breakpoint";
pub const DEBUG_SMALL: &str = "debug_small";
pub const DEBUG_RESTART: &str = "debug_restart";
pub const DEBUG_CONTINUE: &str = "debug_continue";
pub const DEBUG_PAUSE: &str = "debug_pause";
pub const DEBUG_STOP: &str = "debug_stop";
pub const DEBUG_CONSOLE: &str = "debug_console";
pub const DEBUG_DISCONNECT: &str = "debug_disconnect";
pub const START: &str = "start";
pub const RUN_ERRORS: &str = "run_errors";
pub const UNSAVED: &str = "unsaved";
pub const WARNING: &str = "warning";
pub const TERMINAL: &str = "terminal";
pub const SETTINGS: &str = "settings";
pub const LIGHTBULB: &str = "lightbulb";
pub const EXTENSIONS: &str = "extensions";
pub const KEYBOARD: &str = "keyboard";
pub const BREADCRUMB_SEPARATOR: &str = "breadcrumb_separator";
pub const LOGO: &'static str = "logo";
pub const MENU: &'static str = "menu";
pub const LINK: &'static str = "link";
pub const ERROR: &'static str = "error";
pub const ADD: &'static str = "add";
pub const CLOSE: &'static str = "close";
pub const REMOTE: &'static str = "remote";
pub const PROBLEM: &'static str = "error";
pub const DEBUG: &'static str = "debug";
pub const DEBUG_ALT: &'static str = "debug_alt";
pub const DEBUG_BREAKPOINT: &'static str = "debug_breakpoint";
pub const DEBUG_SMALL: &'static str = "debug_small";
pub const DEBUG_RESTART: &'static str = "debug_restart";
pub const DEBUG_CONTINUE: &'static str = "debug_continue";
pub const DEBUG_STEP_OVER: &'static str = "debug_step_over";
pub const DEBUG_STEP_INTO: &'static str = "debug_step_into";
pub const DEBUG_STEP_OUT: &'static str = "debug_step_out";
pub const DEBUG_PAUSE: &'static str = "debug_pause";
pub const DEBUG_STOP: &'static str = "debug_stop";
pub const DEBUG_CONSOLE: &'static str = "debug_console";
pub const DEBUG_DISCONNECT: &'static str = "debug_disconnect";
pub const START: &'static str = "start";
pub const RUN_ERRORS: &'static str = "run_errors";
pub const UNSAVED: &'static str = "unsaved";
pub const WARNING: &'static str = "warning";
pub const TERMINAL: &'static str = "terminal";
pub const SETTINGS: &'static str = "settings";
pub const LIGHTBULB: &'static str = "lightbulb";
pub const EXTENSIONS: &'static str = "extensions";
pub const KEYBOARD: &'static str = "keyboard";
pub const BREADCRUMB_SEPARATOR: &'static str = "breadcrumb_separator";
pub const SYMBOL_COLOR: &'static str = "symbol_color";
pub const FILE: &str = "file";
pub const FILE_EXPLORER: &str = "file_explorer";
pub const FILE_PICKER_UP: &str = "file_picker_up";
pub const FILE: &'static str = "file";
pub const FILE_EXPLORER: &'static str = "file_explorer";
pub const FILE_PICKER_UP: &'static str = "file_picker_up";
pub const IMAGE_LOADING: &str = "image_loading";
pub const IMAGE_ERROR: &str = "image_error";
pub const IMAGE_LOADING: &'static str = "image_loading";
pub const IMAGE_ERROR: &'static str = "image_error";
pub const SCM: &str = "scm.icon";
pub const SCM_DIFF_MODIFIED: &str = "scm.diff.modified";
pub const SCM_DIFF_ADDED: &str = "scm.diff.added";
pub const SCM_DIFF_REMOVED: &str = "scm.diff.removed";
pub const SCM_DIFF_RENAMED: &str = "scm.diff.renamed";
pub const SCM_CHANGE_ADD: &str = "scm.change.add";
pub const SCM_CHANGE_REMOVE: &str = "scm.change.remove";
pub const SCM: &'static str = "scm.icon";
pub const SCM_DIFF_MODIFIED: &'static str = "scm.diff.modified";
pub const SCM_DIFF_ADDED: &'static str = "scm.diff.added";
pub const SCM_DIFF_REMOVED: &'static str = "scm.diff.removed";
pub const SCM_DIFF_RENAMED: &'static str = "scm.diff.renamed";
pub const SCM_CHANGE_ADD: &'static str = "scm.change.add";
pub const SCM_CHANGE_REMOVE: &'static str = "scm.change.remove";
pub const FOLD: &str = "fold";
pub const FOLD_UP: &str = "fold.up";
pub const FOLD_DOWN: &str = "fold.down";
pub const FOLD: &'static str = "fold";
pub const FOLD_UP: &'static str = "fold.up";
pub const FOLD_DOWN: &'static str = "fold.down";
pub const PALETTE_MENU: &str = "palette.menu";
pub const PALETTE_MENU: &'static str = "palette.menu";
pub const DROPDOWN_ARROW: &str = "dropdown.arrow";
pub const DROPDOWN_ARROW: &'static str = "dropdown.arrow";
pub const LOCATION_BACKWARD: &str = "location.backward";
pub const LOCATION_FORWARD: &str = "location.forward";
pub const PANEL_FOLD_UP: &'static str = "panel.fold-up";
pub const PANEL_FOLD_DOWN: &'static str = "panel.fold-down";
pub const ITEM_OPENED: &str = "item.opened";
pub const ITEM_CLOSED: &str = "item.closed";
pub const LOCATION_BACKWARD: &'static str = "location.backward";
pub const LOCATION_FORWARD: &'static str = "location.forward";
pub const DIRECTORY_CLOSED: &str = "directory.closed";
pub const DIRECTORY_OPENED: &str = "directory.opened";
pub const ITEM_OPENED: &'static str = "item.opened";
pub const ITEM_CLOSED: &'static str = "item.closed";
pub const PANEL_RESTORE: &str = "panel.restore";
pub const PANEL_MAXIMISE: &str = "panel.maximise";
pub const DIRECTORY_CLOSED: &'static str = "directory.closed";
pub const DIRECTORY_OPENED: &'static str = "directory.opened";
pub const SPLIT_HORIZONTAL: &str = "split.horizontal";
pub const PANEL_RESTORE: &'static str = "panel.restore";
pub const PANEL_MAXIMISE: &'static str = "panel.maximise";
pub const TAB_PREVIOUS: &str = "tab.previous";
pub const TAB_NEXT: &str = "tab.next";
pub const SPLIT_HORIZONTAL: &'static str = "split.horizontal";
pub const SIDEBAR_LEFT: &str = "sidebar.left.on";
pub const SIDEBAR_LEFT_OFF: &str = "sidebar.left.off";
pub const SIDEBAR_RIGHT: &str = "sidebar.right.on";
pub const SIDEBAR_RIGHT_OFF: &str = "sidebar.right.off";
pub const TAB_PREVIOUS: &'static str = "tab.previous";
pub const TAB_NEXT: &'static str = "tab.next";
pub const LAYOUT_PANEL: &str = "layout.panel.on";
pub const LAYOUT_PANEL_OFF: &str = "layout.panel.off";
pub const SIDEBAR_LEFT: &'static str = "sidebar.left.on";
pub const SIDEBAR_LEFT_OFF: &'static str = "sidebar.left.off";
pub const SIDEBAR_RIGHT: &'static str = "sidebar.right.on";
pub const SIDEBAR_RIGHT_OFF: &'static str = "sidebar.right.off";
pub const LAYOUT_PANEL: &'static str = "layout.panel.on";
pub const LAYOUT_PANEL_OFF: &'static str = "layout.panel.off";
pub const SEARCH: &'static str = "search.icon";
pub const SEARCH_CLEAR: &'static str = "search.clear";
@@ -91,52 +100,65 @@ impl LapceIcons {
pub const SEARCH_REPLACE: &'static str = "search.replace";
pub const SEARCH_REPLACE_ALL: &'static str = "search.replace_all";
pub const FILE_TYPE_CODE: &str = "file-code";
pub const FILE_TYPE_MEDIA: &str = "file-media";
pub const FILE_TYPE_BINARY: &str = "file-binary";
pub const FILE_TYPE_ARCHIVE: &str = "file-zip";
pub const FILE_TYPE_SUBMODULE: &str = "file-submodule";
pub const FILE_TYPE_SYMLINK_FILE: &str = "file-symlink-file";
pub const FILE_TYPE_SYMLINK_DIRECTORY: &str = "file-symlink-directory";
pub const FILE_TYPE_CODE: &'static str = "file-code";
pub const FILE_TYPE_MEDIA: &'static str = "file-media";
pub const FILE_TYPE_BINARY: &'static str = "file-binary";
pub const FILE_TYPE_ARCHIVE: &'static str = "file-zip";
pub const FILE_TYPE_SUBMODULE: &'static str = "file-submodule";
pub const FILE_TYPE_SYMLINK_FILE: &'static str = "file-symlink-file";
pub const FILE_TYPE_SYMLINK_DIRECTORY: &'static str = "file-symlink-directory";
pub const SYMBOL_KIND_ARRAY: &str = "symbol_kind.array";
pub const SYMBOL_KIND_BOOLEAN: &str = "symbol_kind.boolean";
pub const SYMBOL_KIND_CLASS: &str = "symbol_kind.class";
pub const SYMBOL_KIND_CONSTANT: &str = "symbol_kind.constant";
pub const SYMBOL_KIND_ENUM_MEMBER: &str = "symbol_kind.enum_member";
pub const SYMBOL_KIND_ENUM: &str = "symbol_kind.enum";
pub const SYMBOL_KIND_EVENT: &str = "symbol_kind.event";
pub const SYMBOL_KIND_FIELD: &str = "symbol_kind.field";
pub const SYMBOL_KIND_FILE: &str = "symbol_kind.file";
pub const SYMBOL_KIND_FUNCTION: &str = "symbol_kind.function";
pub const SYMBOL_KIND_INTERFACE: &str = "symbol_kind.interface";
pub const SYMBOL_KIND_KEY: &str = "symbol_kind.key";
pub const SYMBOL_KIND_METHOD: &str = "symbol_kind.method";
pub const SYMBOL_KIND_NAMESPACE: &str = "symbol_kind.namespace";
pub const SYMBOL_KIND_NUMBER: &str = "symbol_kind.number";
pub const SYMBOL_KIND_OBJECT: &str = "symbol_kind.namespace";
pub const SYMBOL_KIND_OPERATOR: &str = "symbol_kind.operator";
pub const SYMBOL_KIND_PROPERTY: &str = "symbol_kind.property";
pub const SYMBOL_KIND_STRING: &str = "symbol_kind.string";
pub const SYMBOL_KIND_STRUCT: &str = "symbol_kind.struct";
pub const SYMBOL_KIND_TYPE_PARAMETER: &str = "symbol_kind.type_parameter";
pub const SYMBOL_KIND_VARIABLE: &str = "symbol_kind.variable";
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";
pub const SYMBOL_KIND_CONSTANT: &'static str = "symbol_kind.constant";
pub const SYMBOL_KIND_ENUM_MEMBER: &'static str = "symbol_kind.enum_member";
pub const SYMBOL_KIND_ENUM: &'static str = "symbol_kind.enum";
pub const SYMBOL_KIND_EVENT: &'static str = "symbol_kind.event";
pub const SYMBOL_KIND_FIELD: &'static str = "symbol_kind.field";
pub const SYMBOL_KIND_FILE: &'static str = "symbol_kind.file";
pub const SYMBOL_KIND_FUNCTION: &'static str = "symbol_kind.function";
pub const SYMBOL_KIND_INTERFACE: &'static str = "symbol_kind.interface";
pub const SYMBOL_KIND_KEY: &'static str = "symbol_kind.key";
pub const SYMBOL_KIND_METHOD: &'static str = "symbol_kind.method";
pub const SYMBOL_KIND_NAMESPACE: &'static str = "symbol_kind.namespace";
pub const SYMBOL_KIND_NUMBER: &'static str = "symbol_kind.number";
pub const SYMBOL_KIND_OBJECT: &'static str = "symbol_kind.namespace";
pub const SYMBOL_KIND_OPERATOR: &'static str = "symbol_kind.operator";
pub const SYMBOL_KIND_PROPERTY: &'static str = "symbol_kind.property";
pub const SYMBOL_KIND_STRING: &'static str = "symbol_kind.string";
pub const SYMBOL_KIND_STRUCT: &'static str = "symbol_kind.struct";
pub const SYMBOL_KIND_TYPE_PARAMETER: &'static str =
"symbol_kind.type_parameter";
pub const SYMBOL_KIND_VARIABLE: &'static str = "symbol_kind.variable";
pub const COMPLETION_ITEM_KIND_CLASS: &str = "completion_item_kind.class";
pub const COMPLETION_ITEM_KIND_CONSTANT: &str = "completion_item_kind.constant";
pub const COMPLETION_ITEM_KIND_ENUM_MEMBER: &str =
pub const COMPLETION_ITEM_KIND_CLASS: &'static str =
"completion_item_kind.class";
pub const COMPLETION_ITEM_KIND_CONSTANT: &'static str =
"completion_item_kind.constant";
pub const COMPLETION_ITEM_KIND_ENUM_MEMBER: &'static str =
"completion_item_kind.enum_member";
pub const COMPLETION_ITEM_KIND_ENUM: &str = "completion_item_kind.enum";
pub const COMPLETION_ITEM_KIND_FIELD: &str = "completion_item_kind.field";
pub const COMPLETION_ITEM_KIND_FUNCTION: &str = "completion_item_kind.function";
pub const COMPLETION_ITEM_KIND_INTERFACE: &str =
pub const COMPLETION_ITEM_KIND_ENUM: &'static str = "completion_item_kind.enum";
pub const COMPLETION_ITEM_KIND_FIELD: &'static str =
"completion_item_kind.field";
pub const COMPLETION_ITEM_KIND_FUNCTION: &'static str =
"completion_item_kind.function";
pub const COMPLETION_ITEM_KIND_INTERFACE: &'static str =
"completion_item_kind.interface";
pub const COMPLETION_ITEM_KIND_KEYWORD: &str = "completion_item_kind.keyword";
pub const COMPLETION_ITEM_KIND_METHOD: &str = "completion_item_kind.method";
pub const COMPLETION_ITEM_KIND_MODULE: &str = "completion_item_kind.module";
pub const COMPLETION_ITEM_KIND_PROPERTY: &str = "completion_item_kind.property";
pub const COMPLETION_ITEM_KIND_SNIPPET: &str = "completion_item_kind.snippet";
pub const COMPLETION_ITEM_KIND_STRING: &str = "completion_item_kind.string";
pub const COMPLETION_ITEM_KIND_STRUCT: &str = "completion_item_kind.struct";
pub const COMPLETION_ITEM_KIND_VARIABLE: &str = "completion_item_kind.variable";
pub const COMPLETION_ITEM_KIND_KEYWORD: &'static str =
"completion_item_kind.keyword";
pub const COMPLETION_ITEM_KIND_METHOD: &'static str =
"completion_item_kind.method";
pub const COMPLETION_ITEM_KIND_MODULE: &'static str =
"completion_item_kind.module";
pub const COMPLETION_ITEM_KIND_PROPERTY: &'static str =
"completion_item_kind.property";
pub const COMPLETION_ITEM_KIND_SNIPPET: &'static str =
"completion_item_kind.snippet";
pub const COMPLETION_ITEM_KIND_STRING: &'static str =
"completion_item_kind.string";
pub const COMPLETION_ITEM_KIND_STRUCT: &'static str =
"completion_item_kind.struct";
pub const COMPLETION_ITEM_KIND_VARIABLE: &'static str =
"completion_item_kind.variable";
}
+224 -20
View File
@@ -1,10 +1,26 @@
use std::path::{Path, PathBuf};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
/// Returns the first item yielded from `items` if at least one item is yielded, all yielded items
/// are `Some`, and all yielded items compare equal, else returns `None`.
fn try_all_equal_value<T: PartialEq, I: IntoIterator<Item = Option<T>>>(
items: I,
) -> Option<T> {
let mut items = items.into_iter();
let first = items.next().flatten()?;
items.try_fold(first, |initial_item, item| {
item.and_then(|item| (item == initial_item).then_some(initial_item))
})
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
#[serde(rename_all = "kebab-case", default)]
pub struct IconThemeConfig {
#[serde(skip)]
pub path: PathBuf,
@@ -17,23 +33,211 @@ pub struct IconThemeConfig {
}
impl IconThemeConfig {
pub fn resolve_path_to_icon(&self, path: &Path) -> Option<PathBuf> {
if let Some((_, icon)) = self.filename.get_key_value(
path.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default(),
) {
Some(self.path.join(icon))
} else if let Some((_, icon)) = self.extension.get_key_value(
path.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default(),
) {
Some(self.path.join(icon))
} else {
None
}
/// If all paths in `paths` have the same file type (as determined by the file name or
/// extension), and there is an icon associated with that file type, returns the path of the
/// icon.
pub fn resolve_path_to_icon(&self, paths: &[&Path]) -> Option<PathBuf> {
let file_names = paths
.iter()
.map(|path| path.file_name().and_then(OsStr::to_str));
let file_name_icon = try_all_equal_value(file_names)
.and_then(|file_name| self.filename.get(file_name));
file_name_icon
.or_else(|| {
let extensions = paths
.iter()
.map(|path| path.extension().and_then(OsStr::to_str));
try_all_equal_value(extensions)
.and_then(|extension| self.extension.get(extension))
})
.map(|icon| self.path.join(icon))
}
}
#[cfg(test)]
mod tests {
use super::IconThemeConfig;
use crate::config::icon_theme::try_all_equal_value;
#[test]
fn try_all_equal_value_empty_none() {
assert_eq!(Option::<u32>::None, try_all_equal_value([]));
}
#[test]
fn try_all_equal_value_any_none_none() {
assert_eq!(Option::<u32>::None, try_all_equal_value([None]));
assert_eq!(
Option::<i32>::None,
try_all_equal_value([None, Some(1), Some(1)])
);
assert_eq!(Option::<u64>::None, try_all_equal_value([Some(0), None]));
assert_eq!(
Option::<u8>::None,
try_all_equal_value([Some(3), Some(3), None, Some(3)])
);
}
#[test]
fn try_all_equal_value_any_different_none() {
assert_eq!(Option::<u32>::None, try_all_equal_value([Some(1), Some(2)]));
assert_eq!(
Option::<u128>::None,
try_all_equal_value([Some(1), Some(10), Some(1)])
);
assert_eq!(
Option::<i16>::None,
try_all_equal_value([Some(3), Some(3), Some(3), Some(3), Some(2)])
);
assert_eq!(
Option::<i64>::None,
try_all_equal_value([Some(5), Some(4), Some(4), Some(4), Some(4)])
);
assert_eq!(
Option::<i128>::None,
try_all_equal_value([Some(3), Some(0), Some(9), Some(20), Some(1)])
);
}
#[test]
fn try_all_equal_value_all_same_some() {
assert_eq!(Option::<u32>::Some(1), try_all_equal_value([Some(1)]));
assert_eq!(Option::<i16>::Some(-2), try_all_equal_value([Some(-2); 2]));
assert_eq!(Option::<i128>::Some(0), try_all_equal_value([Some(0); 3]));
assert_eq!(Option::<u8>::Some(30), try_all_equal_value([Some(30); 57]));
}
fn get_icon_theme_config() -> IconThemeConfig {
IconThemeConfig {
path: "icons".to_owned().into(),
filename: [("Makefile", "makefile.svg"), ("special.rs", "special.svg")]
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.into(),
extension: [("rs", "rust.svg"), ("c", "c.svg"), ("py", "python.svg")]
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.into(),
..Default::default()
}
}
#[test]
fn resolve_path_to_icon_no_paths_none() {
let icon_theme_config = get_icon_theme_config();
assert_eq!(None, icon_theme_config.resolve_path_to_icon(&[]));
}
#[test]
fn resolve_path_to_icon_different_none() {
let icon_theme_config = get_icon_theme_config();
assert_eq!(
None,
icon_theme_config
.resolve_path_to_icon(&["foo.rs", "bar.c"].map(AsRef::as_ref))
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(
&["/some/path/main.py", "other/path.py", "dir1/./dir2/file.rs"]
.map(AsRef::as_ref)
)
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(
&["/root/Makefile", "dir/dir/special.rs", "../../main.rs"]
.map(AsRef::as_ref)
)
);
assert_eq!(
None,
icon_theme_config
.resolve_path_to_icon(&["main.c", "foo.txt"].map(AsRef::as_ref))
);
}
#[test]
fn resolve_path_to_icon_no_match_none() {
let icon_theme_config = get_icon_theme_config();
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(&["foo"].map(AsRef::as_ref))
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(
&["/some/path/file.txt", "other/path.txt"].map(AsRef::as_ref)
)
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(
&["folder/file", "/home/user/file", "../../file"].map(AsRef::as_ref)
)
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(&[".."].map(AsRef::as_ref))
);
assert_eq!(
None,
icon_theme_config.resolve_path_to_icon(&["."].map(AsRef::as_ref))
);
}
#[test]
fn resolve_path_to_icon_file_name_match_some() {
let icon_theme_config = get_icon_theme_config();
assert_eq!(
Some("icons/makefile.svg".to_owned().into()),
icon_theme_config.resolve_path_to_icon(&["Makefile"].map(AsRef::as_ref))
);
assert_eq!(
Some("icons/makefile.svg".to_owned().into()),
icon_theme_config.resolve_path_to_icon(
&[
"baz/Makefile",
"/foo/bar/dir/Makefile",
".././/././Makefile"
]
.map(AsRef::as_ref)
)
);
assert_eq!(
Some("icons/special.svg".to_owned().into()),
icon_theme_config.resolve_path_to_icon(
&["dir/special.rs", "/dir1/dir2/..//./special.rs"]
.map(AsRef::as_ref)
)
);
}
#[test]
fn resolve_path_to_icon_extension_match_some() {
let icon_theme_config = get_icon_theme_config();
assert_eq!(
Some("icons/python.svg".to_owned().into()),
icon_theme_config
.resolve_path_to_icon(&["source.py"].map(AsRef::as_ref))
);
assert_eq!(
Some("icons/rust.svg".to_owned().into()),
icon_theme_config.resolve_path_to_icon(
&["/home/user/main.rs", "../../special.rs.rs", "special.rs"]
.map(AsRef::as_ref)
)
);
assert_eq!(
Some("icons/c.svg".to_owned().into()),
icon_theme_config.resolve_path_to_icon(
&["/dir1/Makefile.c", "../main.c"].map(AsRef::as_ref)
)
);
}
}
View File
+2 -1
View File
@@ -11,6 +11,7 @@ use crate::config::LOGO;
const CODICONS_ICONS_DIR: Dir = include_dir!("../icons/codicons");
const LAPCE_ICONS_DIR: Dir = include_dir!("../icons/lapce");
#[derive(Debug, Clone)]
pub struct SvgStore {
svgs: HashMap<String, String>,
svgs_on_disk: HashMap<PathBuf, Option<String>>,
@@ -39,7 +40,7 @@ impl SvgStore {
pub fn get_default_svg(&mut self, name: &str) -> String {
if !self.svgs.contains_key(name) {
let file = if name == "lapce_remote.svg" {
let file = if name == "lapce_remote.svg" || name == "lapce_logo.svg" {
LAPCE_ICONS_DIR.get_file(name).unwrap()
} else {
CODICONS_ICONS_DIR
+44 -3
View File
@@ -18,15 +18,31 @@ pub struct TerminalConfig {
#[field_names(
desc = "Set the terminal line height, If 0, it uses editor line height"
)]
pub line_height: usize,
#[field_names(desc = "Set the terminal Shell")]
pub shell: String,
pub line_height: f64,
#[field_names(skip)]
pub profiles: HashMap<String, TerminalProfile>,
#[field_names(skip)]
pub default_profile: HashMap<String, String>,
#[serde(skip)]
#[field_names(skip)]
pub indexed_colors: Arc<HashMap<u8, Color>>,
}
#[derive(FieldNames, Debug, Clone, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct TerminalProfile {
#[field_names(desc = "Command to execute when launching terminal")]
pub command: Option<String>,
#[field_names(desc = "Arguments passed to command")]
pub arguments: Option<Vec<String>>,
#[field_names(desc = "Command to execute when launching terminal")]
pub workdir: Option<std::path::PathBuf>,
#[field_names(desc = "Arguments passed to command")]
pub environment: Option<HashMap<String, String>>,
}
impl TerminalConfig {
pub fn get_indexed_colors(&mut self) {
let mut indexed_colors = HashMap::new();
@@ -57,4 +73,29 @@ impl TerminalConfig {
self.indexed_colors = Arc::new(indexed_colors);
}
pub fn get_default_profile(
&self,
) -> Option<lapce_rpc::terminal::TerminalProfile> {
let profile = self.profiles.get(
self.default_profile
.get(std::env::consts::OS)
.unwrap_or(&String::from("default")),
)?;
let workdir = if let Some(workdir) = &profile.workdir {
url::Url::parse(&workdir.display().to_string()).ok()
} else {
None
};
let profile = profile.clone();
Some(lapce_rpc::terminal::TerminalProfile {
name: std::env::consts::OS.to_string(),
command: profile.command,
arguments: profile.arguments,
workdir,
environment: profile.environment,
})
}
}
+36 -2
View File
@@ -33,8 +33,8 @@ pub struct UIConfig {
#[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")]
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"
@@ -48,6 +48,32 @@ pub struct UIConfig {
#[field_names(desc = "Set the line height for list items")]
list_line_height: usize,
#[field_names(desc = "Set position of the close button in editor tabs")]
pub tab_close_button: TabCloseButton,
#[field_names(desc = "Display the Open Editors section in the explorer")]
pub open_editors_visible: bool,
}
#[derive(
Debug,
Clone,
Copy,
Deserialize,
Serialize,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
strum_macros::VariantNames,
)]
pub enum TabCloseButton {
Left,
#[default]
Right,
Off,
}
impl UIConfig {
@@ -76,4 +102,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)
}
}
}
+124 -122
View File
@@ -1,5 +1,6 @@
use std::{
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
@@ -8,6 +9,7 @@ use crossbeam_channel::{unbounded, Sender};
use floem::peniko::kurbo::Vec2;
use lapce_core::directory::Directory;
use lapce_rpc::plugin::VoltID;
use sha2::{Digest, Sha256};
use crate::{
app::{AppData, AppInfo},
@@ -18,6 +20,14 @@ use crate::{
workspace::{LapceWorkspace, WorkspaceInfo},
};
const APP: &str = "app";
const WINDOW: &str = "window";
const WORKSPACE_INFO: &str = "workspace_info";
const WORKSPACE_FILES: &str = "workspace_files";
const PANEL_ORDERS: &str = "panel_orders";
const DISABLED_VOLTS: &str = "disabled_volts";
const RECENT_WORKSPACES: &str = "recent_workspaces";
pub enum SaveEvent {
App(AppInfo),
Workspace(LapceWorkspace, WorkspaceInfo),
@@ -30,24 +40,26 @@ pub enum SaveEvent {
#[derive(Clone)]
pub struct LapceDb {
folder: PathBuf,
workspace_folder: PathBuf,
save_tx: Sender<SaveEvent>,
sled_db: Option<sled::Db>,
}
impl LapceDb {
pub fn new() -> Result<Self> {
let path = Directory::config_directory()
let folder = Directory::config_directory()
.ok_or_else(|| anyhow!("can't get config directory"))?
.join("lapce.db");
.join("db");
let workspace_folder = folder.join("workspaces");
let _ = std::fs::create_dir_all(&workspace_folder);
let (save_tx, save_rx) = unbounded();
let sled_db = sled::Config::default()
.path(path)
.flush_every_ms(None)
.open()
.ok();
let db = Self { save_tx, sled_db };
let db = Self {
save_tx,
workspace_folder,
folder,
};
let local_db = db.clone();
std::thread::spawn(move || -> Result<()> {
loop {
@@ -81,19 +93,9 @@ impl LapceDb {
Ok(db)
}
fn get_db(&self) -> Result<&sled::Db> {
self.sled_db
.as_ref()
.ok_or_else(|| anyhow!("didn't open sled db"))
}
pub fn get_disabled_volts(&self) -> Result<Vec<VoltID>> {
let sled_db = self.get_db()?;
let volts = sled_db
.get("disabled_volts")?
.ok_or_else(|| anyhow!("can't find disable volts"))?;
let volts = std::str::from_utf8(&volts)?;
let volts: Vec<VoltID> = serde_json::from_str(volts)?;
let volts = std::fs::read_to_string(self.folder.join(DISABLED_VOLTS))?;
let volts: Vec<VoltID> = serde_json::from_str(&volts)?;
Ok(volts)
}
@@ -112,10 +114,8 @@ impl LapceDb {
}
pub fn insert_disabled_volts(&self, volts: Vec<VoltID>) -> Result<()> {
let sled_db = self.get_db()?;
let volts = serde_json::to_string(&volts)?;
sled_db.insert(b"disabled_volts", volts.as_str())?;
sled_db.flush()?;
let volts = serde_json::to_string_pretty(&volts)?;
std::fs::write(self.folder.join(DISABLED_VOLTS), volts)?;
Ok(())
}
@@ -124,10 +124,13 @@ impl LapceDb {
workspace: Arc<LapceWorkspace>,
volts: Vec<VoltID>,
) -> Result<()> {
let sled_db = self.get_db()?;
let volts = serde_json::to_string(&volts)?;
sled_db.insert(format!("disabled_volts:{workspace}"), volts.as_str())?;
sled_db.flush()?;
let folder = self
.workspace_folder
.join(workspace_folder_name(&workspace));
let _ = std::fs::create_dir_all(&folder);
let volts = serde_json::to_string_pretty(&volts)?;
std::fs::write(folder.join(DISABLED_VOLTS), volts)?;
Ok(())
}
@@ -135,22 +138,16 @@ impl LapceDb {
&self,
workspace: &LapceWorkspace,
) -> Result<Vec<VoltID>> {
let sled_db = self.get_db()?;
let volts = sled_db
.get(format!("disabled_volts:{workspace}"))?
.ok_or_else(|| anyhow!("can't find disable volts"))?;
let volts = std::str::from_utf8(&volts)?;
let volts: Vec<VoltID> = serde_json::from_str(volts)?;
let folder = self.workspace_folder.join(workspace_folder_name(workspace));
let volts = std::fs::read_to_string(folder.join(DISABLED_VOLTS))?;
let volts: Vec<VoltID> = serde_json::from_str(&volts)?;
Ok(volts)
}
pub fn recent_workspaces(&self) -> Result<Vec<LapceWorkspace>> {
let sled_db = self.get_db()?;
let workspaces = sled_db
.get("recent_workspaces")?
.ok_or_else(|| anyhow!("can't find disable volts"))?;
let workspaces = std::str::from_utf8(&workspaces)?;
let workspaces: Vec<LapceWorkspace> = serde_json::from_str(workspaces)?;
let workspaces =
std::fs::read_to_string(self.folder.join(RECENT_WORKSPACES))?;
let workspaces: Vec<LapceWorkspace> = serde_json::from_str(&workspaces)?;
Ok(workspaces)
}
@@ -163,18 +160,7 @@ impl LapceDb {
Ok(())
}
fn insert_doc(&self, info: &DocInfo) -> Result<()> {
let key = format!("{}:{}", info.workspace, info.path.to_str().unwrap_or(""));
let info = serde_json::to_string(info)?;
let sled_db = self.get_db()?;
sled_db.insert(key.as_str(), info.as_str())?;
sled_db.flush()?;
Ok(())
}
fn insert_recent_workspace(&self, workspace: LapceWorkspace) -> Result<()> {
let sled_db = self.get_db()?;
let mut workspaces = self.recent_workspaces().unwrap_or_default();
let mut exits = false;
@@ -197,29 +183,13 @@ impl LapceDb {
workspaces.push(workspace);
}
workspaces.sort_by_key(|w| -(w.last_open as i64));
let workspaces = serde_json::to_string(&workspaces)?;
sled_db.insert("recent_workspaces", workspaces.as_str())?;
sled_db.flush()?;
let workspaces = serde_json::to_string_pretty(&workspaces)?;
std::fs::write(self.folder.join(RECENT_WORKSPACES), workspaces)?;
Ok(())
}
pub fn get_workspace_info(
&self,
workspace: &LapceWorkspace,
) -> Result<WorkspaceInfo> {
let workspace = workspace.to_string();
let sled_db = self.get_db()?;
let info = sled_db
.get(workspace)?
.ok_or_else(|| anyhow!("can't find workspace info"))?;
let info = std::str::from_utf8(&info)?;
let info: WorkspaceInfo = serde_json::from_str(info)?;
Ok(info)
}
pub fn save_window_tab(&self, data: Arc<WindowTabData>) -> Result<()> {
pub fn save_window_tab(&self, data: Rc<WindowTabData>) -> Result<()> {
let workspace = (*data.workspace).clone();
let workspace_info = data.workspace_info();
@@ -230,16 +200,28 @@ impl LapceDb {
Ok(())
}
pub fn get_workspace_info(
&self,
workspace: &LapceWorkspace,
) -> Result<WorkspaceInfo> {
let info = std::fs::read_to_string(
self.workspace_folder
.join(workspace_folder_name(workspace))
.join(WORKSPACE_INFO),
)?;
let info: WorkspaceInfo = serde_json::from_str(&info)?;
Ok(info)
}
fn insert_workspace(
&self,
workspace: &LapceWorkspace,
info: &WorkspaceInfo,
) -> Result<()> {
let workspace = workspace.to_string();
let workspace_info = serde_json::to_string(info)?;
let sled_db = self.get_db()?;
sled_db.insert(workspace.as_str(), workspace_info.as_str())?;
sled_db.flush()?;
let folder = self.workspace_folder.join(workspace_folder_name(workspace));
let _ = std::fs::create_dir_all(&folder);
let workspace_info = serde_json::to_string_pretty(info)?;
std::fs::write(folder.join(WORKSPACE_INFO), workspace_info)?;
Ok(())
}
@@ -255,6 +237,9 @@ impl LapceDb {
.map(|(_, window_data)| window_data.info())
.collect(),
};
if info.windows.is_empty() {
return Ok(());
}
self.save_tx.send(SaveEvent::App(info))?;
@@ -262,15 +247,17 @@ impl LapceDb {
}
pub fn insert_app_info(&self, info: AppInfo) -> Result<()> {
let info = serde_json::to_string(&info)?;
let sled_db = self.get_db()?;
sled_db.insert("app", info.as_str())?;
sled_db.flush()?;
let info = serde_json::to_string_pretty(&info)?;
std::fs::write(self.folder.join(APP), info)?;
Ok(())
}
pub fn insert_app(&self, data: AppData) -> Result<()> {
let windows = data.windows.get_untracked();
if windows.is_empty() {
// insert_app is called after window is closed, so we don't want to store it
return Ok(());
}
for (_, window) in &windows {
let _ = self.insert_window(window.clone());
}
@@ -280,30 +267,33 @@ impl LapceDb {
.map(|(_, window_data)| window_data.info())
.collect(),
};
let info = serde_json::to_string(&info)?;
let sled_db = self.get_db()?;
sled_db.insert("app", info.as_str())?;
sled_db.flush()?;
self.insert_app_info(info)?;
Ok(())
}
pub fn get_app(&self) -> Result<AppInfo> {
let sled_db = self.get_db()?;
let info = sled_db
.get("app")?
.ok_or_else(|| anyhow!("can't find app info"))?;
let info = std::str::from_utf8(&info)?;
let info: AppInfo = serde_json::from_str(info)?;
let info = std::fs::read_to_string(self.folder.join(APP))?;
let mut info: AppInfo = serde_json::from_str(&info)?;
for window in info.windows.iter_mut() {
if window.size.width < 10.0 {
window.size.width = 800.0;
}
if window.size.height < 10.0 {
window.size.width = 600.0;
}
}
Ok(info)
}
pub fn get_window(&self) -> Result<WindowInfo> {
let sled_db = self.get_db()?;
let info = sled_db
.get("window")?
.ok_or_else(|| anyhow!("can't find app info"))?;
let info = std::str::from_utf8(&info)?;
let info: WindowInfo = serde_json::from_str(info)?;
let info = std::fs::read_to_string(self.folder.join(WINDOW))?;
let mut info: WindowInfo = serde_json::from_str(&info)?;
if info.size.width < 10.0 {
info.size.width = 800.0;
}
if info.size.height < 10.0 {
info.size.width = 600.0;
}
Ok(info)
}
@@ -319,14 +309,12 @@ impl LapceDb {
let _ = self.insert_window_tab(window_tab);
}
let info = data.info();
let info = serde_json::to_string(&info)?;
let sled_db = self.get_db()?;
sled_db.insert("window", info.as_str())?;
sled_db.flush()?;
let info = serde_json::to_string_pretty(&info)?;
std::fs::write(self.folder.join(WINDOW), info)?;
Ok(())
}
pub fn insert_window_tab(&self, data: Arc<WindowTabData>) -> Result<()> {
pub fn insert_window_tab(&self, data: Rc<WindowTabData>) -> Result<()> {
let workspace = (*data.workspace).clone();
let workspace_info = data.workspace_info();
@@ -337,19 +325,13 @@ impl LapceDb {
}
pub fn get_panel_orders(&self) -> Result<PanelOrder> {
let sled_db = self.get_db()?;
let panel_orders = sled_db
.get("panel_orders")?
.ok_or_else(|| anyhow!("can't find panel orders"))?;
let panel_orders = std::str::from_utf8(&panel_orders)?;
let mut panel_orders: PanelOrder = serde_json::from_str(panel_orders)?;
let panel_orders = std::fs::read_to_string(self.folder.join(PANEL_ORDERS))?;
let mut panel_orders: PanelOrder = serde_json::from_str(&panel_orders)?;
use strum::IntoEnumIterator;
for kind in PanelKind::iter() {
if kind.position(&panel_orders).is_none() {
let panels = panel_orders
.entry(PanelPosition::LeftTop)
.or_insert_with(im::Vector::new);
let panels = panel_orders.entry(PanelPosition::LeftTop).or_default();
panels.push_back(kind);
}
}
@@ -362,10 +344,8 @@ impl LapceDb {
}
fn insert_panel_orders(&self, order: &PanelOrder) -> Result<()> {
let info = serde_json::to_string(order)?;
let sled_db = self.get_db()?;
sled_db.insert("panel_orders", info.as_str())?;
sled_db.flush()?;
let info = serde_json::to_string_pretty(order)?;
std::fs::write(self.folder.join(PANEL_ORDERS), info)?;
Ok(())
}
@@ -385,18 +365,40 @@ impl LapceDb {
let _ = self.save_tx.send(SaveEvent::Doc(info));
}
fn insert_doc(&self, info: &DocInfo) -> Result<()> {
let folder = self
.workspace_folder
.join(workspace_folder_name(&info.workspace))
.join(WORKSPACE_FILES);
let _ = std::fs::create_dir_all(&folder);
let contents = serde_json::to_string_pretty(info)?;
std::fs::write(folder.join(doc_path_name(&info.path)), contents)?;
Ok(())
}
pub fn get_doc_info(
&self,
workspace: &LapceWorkspace,
path: &Path,
) -> Result<DocInfo> {
let key = format!("{}:{}", workspace, path.to_str().unwrap_or(""));
let sled_db = self.get_db()?;
let info = sled_db
.get(key.as_str())?
.ok_or_else(|| anyhow!("can't find workspace info"))?;
let info = std::str::from_utf8(&info)?;
let info: DocInfo = serde_json::from_str(info)?;
let folder = self
.workspace_folder
.join(workspace_folder_name(workspace))
.join(WORKSPACE_FILES);
let info = std::fs::read_to_string(folder.join(doc_path_name(path)))?;
let info: DocInfo = serde_json::from_str(&info)?;
Ok(info)
}
}
fn workspace_folder_name(workspace: &LapceWorkspace) -> String {
url::form_urlencoded::Serializer::new(String::new())
.append_key_only(&workspace.to_string())
.finish()
}
fn doc_path_name(path: &Path) -> String {
let mut hasher = Sha256::new();
hasher.update(path.to_string_lossy().as_bytes());
format!("{:x}", hasher.finalize())
}
+511 -53
View File
@@ -1,20 +1,28 @@
use std::{
collections::{BTreeMap, HashMap},
fmt::Display,
path::{Path, PathBuf},
path::PathBuf,
rc::Rc,
time::Instant,
};
use floem::reactive::{RwSignal, Scope};
use lapce_rpc::{
dap_types::{
DapId, RunDebugConfig, SourceBreakpoint, StackFrame, Stopped, ThreadId,
},
terminal::TermId,
use dap_types::types::{
self, DapId, RunDebugConfig, SourceBreakpoint, StackFrame, Stopped, ThreadId,
Variable,
};
use floem::{
ext_event::create_ext_action,
reactive::{Memo, RwSignal, Scope},
views::VirtualVector,
};
use lapce_rpc::{proxy::ProxyResponse, terminal::TermId};
use serde::{Deserialize, Serialize};
const DEFAULT_RUN_TOML: &str = include_str!("../../defaults/run.toml");
use crate::{
command::InternalCommand,
editor::location::{EditorLocation, EditorPosition},
window_tab::CommonData,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RunDebugMode {
@@ -38,6 +46,7 @@ pub struct RunDebugProcess {
pub config: RunDebugConfig,
pub stopped: bool,
pub created: Instant,
pub is_prelaunch: bool,
}
#[derive(Deserialize, Serialize)]
@@ -45,33 +54,22 @@ pub struct RunDebugConfigs {
pub configs: Vec<RunDebugConfig>,
}
pub fn run_configs(workspace: Option<&Path>) -> Option<RunDebugConfigs> {
let workspace = workspace?;
let run_toml = workspace.join(".lapce").join("run.toml");
if !run_toml.exists() {
if !workspace.join(".lapce").exists() {
let _ = std::fs::create_dir_all(workspace.join(".lapce"));
}
let _ = std::fs::write(&run_toml, DEFAULT_RUN_TOML);
return None;
}
let content = std::fs::read_to_string(run_toml).ok()?;
let configs: RunDebugConfigs = toml_edit::easy::from_str(&content).ok()?;
Some(configs)
}
#[derive(Clone)]
pub struct RunDebugData {
pub active_term: RwSignal<Option<TermId>>,
pub daps: RwSignal<im::HashMap<DapId, DapData>>,
pub breakpoints: RwSignal<BTreeMap<PathBuf, Vec<LapceBreakpoint>>>,
pub breakpoints: RwSignal<BTreeMap<PathBuf, BTreeMap<usize, LapceBreakpoint>>>,
}
impl RunDebugData {
pub fn new(cx: Scope) -> Self {
let active_term = cx.create_rw_signal(None);
let daps = cx.create_rw_signal(im::HashMap::new());
let breakpoints = cx.create_rw_signal(BTreeMap::new());
pub fn new(
cx: Scope,
breakpoints: RwSignal<BTreeMap<PathBuf, BTreeMap<usize, LapceBreakpoint>>>,
) -> Self {
let active_term: RwSignal<Option<TermId>> = cx.create_rw_signal(None);
let daps: RwSignal<im::HashMap<DapId, DapData>> =
cx.create_rw_signal(im::HashMap::new());
Self {
active_term,
daps,
@@ -88,12 +86,18 @@ impl RunDebugData {
path.to_path_buf(),
breakpoints
.iter()
.map(|b| SourceBreakpoint {
line: b.line + 1,
column: None,
condition: None,
hit_condition: None,
log_message: None,
.filter_map(|(_, b)| {
if b.active {
Some(SourceBreakpoint {
line: b.line + 1,
column: None,
condition: None,
hit_condition: None,
log_message: None,
})
} else {
None
}
})
.collect(),
)
@@ -109,7 +113,7 @@ pub struct StackTraceData {
pub frames_shown: usize,
}
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct LapceBreakpoint {
pub id: Option<usize>,
pub verified: bool,
@@ -117,6 +121,60 @@ pub struct LapceBreakpoint {
pub line: usize,
pub offset: usize,
pub dap_line: Option<usize>,
pub active: bool,
}
#[derive(Clone, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum ScopeOrVar {
Scope(dap_types::Scope),
Var(dap_types::Variable),
}
impl Default for ScopeOrVar {
fn default() -> Self {
ScopeOrVar::Scope(dap_types::Scope::default())
}
}
impl ScopeOrVar {
pub fn name(&self) -> &str {
match self {
ScopeOrVar::Scope(scope) => &scope.name,
ScopeOrVar::Var(var) => &var.name,
}
}
pub fn value(&self) -> Option<&str> {
match self {
ScopeOrVar::Scope(_) => None,
ScopeOrVar::Var(var) => Some(&var.value),
}
}
pub fn ty(&self) -> Option<&str> {
match self {
ScopeOrVar::Scope(_) => None,
ScopeOrVar::Var(var) => var.ty.as_deref(),
}
}
pub fn reference(&self) -> usize {
match self {
ScopeOrVar::Scope(scope) => scope.variables_reference,
ScopeOrVar::Var(var) => var.variables_reference,
}
}
}
#[derive(Clone, Default)]
pub struct DapVariable {
pub item: ScopeOrVar,
pub parent: Vec<usize>,
pub expanded: bool,
pub read: bool,
pub children: Vec<DapVariable>,
pub children_expanded_count: usize,
}
#[derive(Clone)]
@@ -126,19 +184,63 @@ pub struct DapData {
pub stopped: RwSignal<bool>,
pub thread_id: RwSignal<Option<ThreadId>>,
pub stack_traces: RwSignal<BTreeMap<ThreadId, StackTraceData>>,
pub variables_id: RwSignal<usize>,
pub variables: RwSignal<DapVariable>,
pub breakline: Memo<Option<(usize, PathBuf)>>,
pub common: Rc<CommonData>,
}
impl DapData {
pub fn new(cx: Scope, dap_id: DapId, term_id: TermId) -> Self {
pub fn new(
cx: Scope,
dap_id: DapId,
term_id: TermId,
common: Rc<CommonData>,
) -> Self {
let stopped = cx.create_rw_signal(false);
let thread_id = cx.create_rw_signal(None);
let stack_traces = cx.create_rw_signal(BTreeMap::new());
let stack_traces: RwSignal<BTreeMap<ThreadId, StackTraceData>> =
cx.create_rw_signal(BTreeMap::new());
let breakline = cx.create_memo(move |_| {
let thread_id = thread_id.get();
if let Some(thread_id) = thread_id {
let trace = stack_traces
.with(|stack_traces| stack_traces.get(&thread_id).cloned());
if let Some(trace) = trace {
let breakline = trace.frames.with(|f| {
f.get(0)
.and_then(|f| {
f.source
.as_ref()
.map(|s| (f.line.saturating_sub(1), s))
})
.and_then(|(line, s)| s.path.clone().map(|p| (line, p)))
});
return breakline;
}
None
} else {
None
}
});
Self {
term_id,
dap_id,
stopped,
thread_id,
stack_traces,
variables_id: cx.create_rw_signal(0),
variables: cx.create_rw_signal(DapVariable {
item: ScopeOrVar::Scope(dap_types::Scope::default()),
parent: Vec::new(),
expanded: true,
read: true,
children: Vec::new(),
children_expanded_count: 0,
}),
breakline,
common,
}
}
@@ -147,6 +249,7 @@ impl DapData {
cx: Scope,
stopped: &Stopped,
stack_traces: &HashMap<ThreadId, Vec<StackFrame>>,
variables: &[(dap_types::Scope, Vec<Variable>)],
) {
self.stopped.set(true);
self.thread_id.update(|thread_id| {
@@ -154,26 +257,381 @@ impl DapData {
});
let main_thread_id = self.thread_id.get_untracked();
self.stack_traces.update(|current_stack_traces| {
current_stack_traces.retain(|t, _| stack_traces.contains_key(t));
for (thread_id, frames) in stack_traces {
let is_main_thread = main_thread_id.as_ref() == Some(thread_id);
if let Some(current) = current_stack_traces.get_mut(thread_id) {
current.frames.set(frames.into());
if is_main_thread {
current.expanded.set(true);
let mut current_stack_traces = self.stack_traces.get_untracked();
current_stack_traces.retain(|t, _| stack_traces.contains_key(t));
for (thread_id, frames) in stack_traces {
let is_main_thread = main_thread_id.as_ref() == Some(thread_id);
if is_main_thread {
if let Some(frame) = frames.first() {
if let Some(path) =
frame.source.as_ref().and_then(|source| source.path.clone())
{
self.common.internal_command.send(
InternalCommand::JumpToLocation {
location: EditorLocation {
path,
position: Some(EditorPosition::Line(
frame.line.saturating_sub(1),
)),
scroll_offset: None,
ignore_unconfirmed: false,
same_editor_tab: false,
},
},
);
}
}
}
if let Some(current) = current_stack_traces.get_mut(thread_id) {
current.frames.set(frames.into());
if is_main_thread {
current.expanded.set(true);
}
} else {
current_stack_traces.insert(
*thread_id,
StackTraceData {
expanded: cx.create_rw_signal(is_main_thread),
frames: cx.create_rw_signal(frames.into()),
frames_shown: 20,
},
);
}
}
self.stack_traces.set(current_stack_traces);
self.variables.update(|dap_var| {
dap_var.children = variables
.iter()
.enumerate()
.map(|(i, (scope, vars))| DapVariable {
item: ScopeOrVar::Scope(scope.to_owned()),
parent: Vec::new(),
expanded: i == 0,
read: true,
children: vars
.iter()
.map(|var| DapVariable {
item: ScopeOrVar::Var(var.to_owned()),
parent: vec![scope.variables_reference],
expanded: false,
read: false,
children: Vec::new(),
children_expanded_count: 0,
})
.collect(),
children_expanded_count: if i == 0 { vars.len() } else { 0 },
})
.collect();
dap_var.children_expanded_count = dap_var
.children
.iter()
.map(|v| v.children_expanded_count + 1)
.sum::<usize>();
});
}
pub fn toggle_expand(&self, parent: Vec<usize>, reference: usize) {
self.variables_id.update(|id| {
*id += 1;
});
self.variables.update(|variables| {
if let Some(var) = variables.get_var_mut(&parent, reference) {
if var.expanded {
var.expanded = false;
variables.update_count_recursive(&parent, reference);
} else {
current_stack_traces.insert(
*thread_id,
StackTraceData {
expanded: cx.create_rw_signal(is_main_thread),
frames: cx.create_rw_signal(frames.into()),
frames_shown: 20,
},
);
var.expanded = true;
if !var.read {
var.read = true;
self.read_var_children(&parent, reference);
} else {
variables.update_count_recursive(&parent, reference);
}
}
}
});
}
fn read_var_children(&self, parent: &[usize], reference: usize) {
let root = self.variables;
let parent = parent.to_vec();
let variables_id = self.variables_id;
let send = create_ext_action(self.common.scope, move |result| {
if let Ok(ProxyResponse::DapVariableResponse { varialbes }) = result {
variables_id.update(|id| {
*id += 1;
});
root.update(|root| {
if let Some(var) = root.get_var_mut(&parent, reference) {
let mut new_parent = parent.clone();
new_parent.push(reference);
var.read = true;
var.children = varialbes
.into_iter()
.map(|v| DapVariable {
item: ScopeOrVar::Var(v),
parent: new_parent.clone(),
expanded: false,
read: false,
children: Vec::new(),
children_expanded_count: 0,
})
.collect();
root.update_count_recursive(&parent, reference);
}
});
}
});
self.common
.proxy
.dap_variable(self.dap_id, reference, move |result| {
send(result);
});
}
}
pub struct DapVariableViewdata {
pub item: ScopeOrVar,
pub parent: Vec<usize>,
pub expanded: bool,
pub level: usize,
}
impl VirtualVector<DapVariableViewdata> for DapVariable {
fn total_len(&self) -> usize {
self.children_expanded_count
}
fn slice(
&mut self,
range: std::ops::Range<usize>,
) -> impl Iterator<Item = DapVariableViewdata> {
let min = range.start;
let max = range.end;
let mut i = 0;
let mut view_items = Vec::new();
for item in self.children.iter() {
i = item.append_view_slice(&mut view_items, min, max, i + 1, 0);
if i > max {
return view_items.into_iter();
}
}
view_items.into_iter()
}
}
impl DapVariable {
pub fn append_view_slice(
&self,
view_items: &mut Vec<DapVariableViewdata>,
min: usize,
max: usize,
current: usize,
level: usize,
) -> usize {
if current > max {
return current;
}
if current + self.children_expanded_count < min {
return current + self.children_expanded_count;
}
let mut i = current;
if current >= min {
view_items.push(DapVariableViewdata {
item: self.item.clone(),
parent: self.parent.clone(),
expanded: self.expanded,
level,
});
}
if self.expanded {
for item in self.children.iter() {
i = item.append_view_slice(view_items, min, max, i + 1, level + 1);
if i > max {
return i;
}
}
}
i
}
pub fn get_var_mut(
&mut self,
parent: &[usize],
reference: usize,
) -> Option<&mut DapVariable> {
let parent = if parent.is_empty() {
self
} else {
parent.iter().try_fold(self, |item, parent| {
item.children
.iter_mut()
.find(|c| c.item.reference() == *parent)
})?
};
parent
.children
.iter_mut()
.find(|c| c.item.reference() == reference)
}
pub fn update_count_recursive(&mut self, parent: &[usize], reference: usize) {
let mut parent = parent.to_vec();
self.update_count(&parent, reference);
while let Some(reference) = parent.pop() {
self.update_count(&parent, reference);
}
self.children_expanded_count = self
.children
.iter()
.map(|item| item.children_expanded_count + 1)
.sum::<usize>();
}
pub fn update_count(
&mut self,
parent: &[usize],
reference: usize,
) -> Option<()> {
let var = self.get_var_mut(parent, reference)?;
var.children_expanded_count = if var.expanded {
var.children
.iter()
.map(|item| item.children_expanded_count + 1)
.sum::<usize>()
} else {
0
};
Some(())
}
}
#[cfg(test)]
mod tests {
use lapce_rpc::dap_types::{Scope, Variable};
use super::{DapVariable, ScopeOrVar};
#[test]
fn test_update_count() {
let variables = vec![
(
Scope {
variables_reference: 0,
..Default::default()
},
vec![
Variable {
variables_reference: 3,
..Default::default()
},
Variable {
variables_reference: 4,
..Default::default()
},
],
),
(
Scope {
variables_reference: 1,
..Default::default()
},
vec![
Variable {
variables_reference: 5,
..Default::default()
},
Variable {
variables_reference: 6,
..Default::default()
},
],
),
(
Scope {
variables_reference: 2,
..Default::default()
},
vec![
Variable {
variables_reference: 7,
..Default::default()
},
Variable {
variables_reference: 8,
..Default::default()
},
],
),
];
let mut root = DapVariable {
item: ScopeOrVar::Scope(Scope::default()),
parent: Vec::new(),
expanded: true,
read: true,
children: variables
.iter()
.map(|(scope, vars)| DapVariable {
item: ScopeOrVar::Scope(scope.to_owned()),
parent: Vec::new(),
expanded: true,
read: true,
children: vars
.iter()
.map(|var| DapVariable {
item: ScopeOrVar::Var(var.to_owned()),
parent: vec![scope.variables_reference],
expanded: false,
read: false,
children: Vec::new(),
children_expanded_count: 0,
})
.collect(),
children_expanded_count: vars.len(),
})
.collect(),
children_expanded_count: 0,
};
root.children_expanded_count = root
.children
.iter()
.map(|v| v.children_expanded_count + 1)
.sum::<usize>();
assert_eq!(root.children_expanded_count, 9);
let var = root.get_var_mut(&[0], 3).unwrap();
var.expanded = true;
var.read = true;
var.children = vec![
Variable {
variables_reference: 9,
..Default::default()
},
Variable {
variables_reference: 10,
..Default::default()
},
]
.iter()
.map(|var| DapVariable {
item: ScopeOrVar::Var(var.to_owned()),
parent: vec![0, 3],
expanded: false,
read: false,
children: Vec::new(),
children_expanded_count: 0,
})
.collect();
root.update_count_recursive(&[0], 3);
let var = root.get_var_mut(&[0], 3).unwrap();
assert_eq!(var.children_expanded_count, 2);
let var = root.get_var_mut(&[], 0).unwrap();
assert_eq!(var.children_expanded_count, 4);
assert_eq!(root.children_expanded_count, 11);
}
}
+1495 -938
View File
File diff suppressed because it is too large Load Diff
-126
View File
@@ -1,126 +0,0 @@
use floem::peniko::Color;
use lsp_types::DiagnosticSeverity;
use smallvec::SmallVec;
/// `PhantomText` is for text that is not in the actual document, but should be rendered with it.
/// Ex: Inlay hints, IME text, error lens' diagnostics, etc
pub struct PhantomText {
/// The kind is currently used for sorting the phantom text on a line
pub kind: PhantomTextKind,
/// Column on the line that the phantom text should be displayed at
pub col: usize,
pub text: String,
pub font_size: Option<usize>,
// font_family: Option<FontFamily>,
pub fg: Option<Color>,
pub bg: Option<Color>,
pub under_line: Option<Color>,
}
#[derive(Ord, Eq, PartialEq, PartialOrd)]
pub enum PhantomTextKind {
/// Input methods
Ime,
/// Completion lens
Completion,
/// Inlay hints supplied by an LSP/PSP (like type annotations)
InlayHint,
/// Error lens
Diagnostic,
}
/// Information about the phantom text on a specific line.
/// This has various utility functions for transforming a coordinate (typically a column) into the
/// resulting coordinate after the phantom text is combined with the line's real content.
#[derive(Default)]
pub struct PhantomTextLine {
/// This uses a smallvec because most lines rarely have more than a couple phantom texts
pub text: SmallVec<[PhantomText; 6]>,
/// Maximum diagnostic severity, so that we can color the background as an error if there is a
/// warning and error on the line. (For error lens)
pub max_severity: Option<DiagnosticSeverity>,
}
impl PhantomTextLine {
/// Translate a column position into the text into what it would be after combining
pub fn col_at(&self, pre_col: usize) -> usize {
let mut last = pre_col;
for (col_shift, size, col, _) in self.offset_size_iter() {
if pre_col >= col {
last = pre_col + col_shift + size;
}
}
last
}
/// Translate a column position into the text into what it would be after combining
/// If before_cursor is false and the cursor is right at the start then it will stay there
pub fn col_after(&self, pre_col: usize, before_cursor: bool) -> usize {
let mut last = pre_col;
for (col_shift, size, col, _) in self.offset_size_iter() {
if pre_col > col || (pre_col == col && before_cursor) {
last = pre_col + col_shift + size;
}
}
last
}
/// Translate a column position into the position it would be before combining
pub fn before_col(&self, col: usize) -> usize {
let mut last = col;
for (col_shift, size, hint_col, _) in self.offset_size_iter() {
let shifted_start = hint_col + col_shift;
let shifted_end = shifted_start + size;
if col >= shifted_start {
if col >= shifted_end {
last = col - col_shift - size;
} else {
last = hint_col;
}
}
}
last
}
/// Insert the hints at their positions in the text
pub fn combine_with_text(&self, text: String) -> String {
let mut text = text;
let mut col_shift = 0;
for phantom in self.text.iter() {
let location = phantom.col + col_shift;
// Stop iterating if the location is bad
if text.get(location..).is_none() {
return text;
}
text.insert_str(location, &phantom.text);
col_shift += phantom.text.len();
}
text
}
/// Iterator over (col_shift, size, hint, pre_column)
/// Note that this only iterates over the ordered text, since those depend on the text for where
/// they'll be positioned
pub fn offset_size_iter(
&self,
) -> impl Iterator<Item = (usize, usize, usize, &PhantomText)> + '_ {
let mut col_shift = 0;
self.text.iter().map(move |phantom| {
let pre_col_shift = col_shift;
col_shift += phantom.text.len();
(
pre_col_shift,
col_shift - pre_col_shift,
phantom.col,
phantom,
)
})
}
}
+1877 -819
View File
File diff suppressed because it is too large Load Diff
+262 -272
View File
@@ -1,12 +1,14 @@
use std::sync::atomic;
use std::{rc::Rc, sync::atomic};
use floem::{
event::EventListener,
ext_event::create_ext_action,
reactive::{RwSignal, Scope},
style::{CursorStyle, Style},
view::View,
views::{clip, empty, label, list, stack, svg, Decorators},
style::CursorStyle,
views::{
clip, dyn_stack, editor::id::EditorId, empty, label, stack, svg, Decorators,
},
View,
};
use lapce_core::buffer::{
diff::{expand_diff_lines, rope_diff, DiffExpand, DiffLines},
@@ -16,17 +18,16 @@ 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::{DocContent, Document},
id::{DiffEditorId, EditorId, EditorTabId},
main_split::MainSplitData,
doc::{Doc, DocContent},
id::{DiffEditorId, EditorTabId},
main_split::{Editors, MainSplitData},
wave::wave_box,
window_tab::CommonData,
};
use super::{EditorData, EditorViewKind};
#[derive(Clone)]
pub struct DiffInfo {
pub is_right: bool,
@@ -53,43 +54,41 @@ impl DiffEditorInfo {
let data = data.clone();
let common = data.common.clone();
move |content: &DocContent| match content {
DocContent::File(path) => {
let (doc, _) = data.get_doc(path.clone());
DocContent::File { path, .. } => {
let (doc, _) = data.get_doc(path.clone(), None);
doc
}
DocContent::Local => cx.create_rw_signal(Document::new_local(
cx,
common.find.clone(),
common.proxy.clone(),
common.config,
)),
DocContent::Local => {
Rc::new(Doc::new_local(cx, data.editors, common.clone()))
}
DocContent::History(history) => {
let doc = Document::new_hisotry(
let doc = Doc::new_history(
cx,
content.clone(),
common.find.clone(),
common.proxy.clone(),
common.config,
data.editors,
common.clone(),
);
let doc = doc.scope.create_rw_signal(doc);
let doc = Rc::new(doc);
let send = create_ext_action(cx, move |result| {
if let Ok(ProxyResponse::BufferHeadResponse {
content,
..
}) = result
{
doc.update(|doc| {
{
let doc = doc.clone();
let send = create_ext_action(cx, move |result| {
if let Ok(ProxyResponse::BufferHeadResponse {
content,
..
}) = result
{
doc.init_content(Rope::from(content));
});
}
});
common.proxy.get_buffer_head(
history.path.clone(),
move |result| {
send(result);
},
);
}
});
common.proxy.get_buffer_head(
history.path.clone(),
move |result| {
send(result);
},
);
}
doc
}
DocContent::Scratch { name, .. } => {
@@ -97,16 +96,15 @@ impl DiffEditorInfo {
id: BufferId::next(),
name: name.to_string(),
};
let doc = Document::new_content(
let doc = Doc::new_content(
cx,
doc_content,
common.find.clone(),
common.proxy.clone(),
common.config,
data.editors,
common.clone(),
);
let doc = doc.scope.create_rw_signal(doc);
let doc = Rc::new(doc);
data.scratch_docs.update(|scratch_docs| {
scratch_docs.insert(name.to_string(), doc);
scratch_docs.insert(name.to_string(), doc.clone());
});
doc
}
@@ -122,6 +120,7 @@ impl DiffEditorInfo {
editor_tab_id,
left_doc,
right_doc,
data.editors,
data.common.clone(),
);
@@ -136,10 +135,11 @@ impl DiffEditorInfo {
#[derive(Clone)]
pub struct DiffEditorData {
pub id: DiffEditorId,
pub editor_tab_id: EditorTabId,
pub editor_tab_id: RwSignal<EditorTabId>,
pub scope: Scope,
pub left: RwSignal<EditorData>,
pub right: RwSignal<EditorData>,
pub left: EditorData,
pub right: EditorData,
pub confirmed: RwSignal<bool>,
pub focus_right: RwSignal<bool>,
}
@@ -148,23 +148,33 @@ impl DiffEditorData {
cx: Scope,
id: DiffEditorId,
editor_tab_id: EditorTabId,
left_doc: RwSignal<Document>,
right_doc: RwSignal<Document>,
common: CommonData,
left_doc: Rc<Doc>,
right_doc: Rc<Doc>,
editors: Editors,
common: Rc<CommonData>,
) -> Self {
let cx = cx.create_child();
let left =
EditorData::new(cx, None, EditorId::next(), left_doc, common.clone());
let left = left.scope.create_rw_signal(left);
let right = EditorData::new(cx, None, EditorId::next(), right_doc, common);
let right = right.scope.create_rw_signal(right);
let confirmed = cx.create_rw_signal(false);
// TODO: ensure that left/right are cleaned up
let [left, right] = [left_doc, right_doc].map(|doc| {
editors.make_from_doc(
cx,
doc,
None,
Some((editor_tab_id, id)),
Some(confirmed),
common.clone(),
)
});
let data = Self {
id,
editor_tab_id,
editor_tab_id: cx.create_rw_signal(editor_tab_id),
scope: cx,
left,
right,
confirmed,
focus_right: cx.create_rw_signal(true),
};
@@ -175,14 +185,8 @@ impl DiffEditorData {
pub fn diff_editor_info(&self) -> DiffEditorInfo {
DiffEditorInfo {
left_content: self.left.get_untracked().view.doc.get_untracked().content,
right_content: self
.right
.get_untracked()
.view
.doc
.get_untracked()
.content,
left_content: self.left.doc().content.get_untracked(),
right_content: self.right.doc().content.get_untracked(),
}
}
@@ -191,24 +195,31 @@ impl DiffEditorData {
cx: Scope,
editor_tab_id: EditorTabId,
diff_editor_id: EditorId,
editors: Editors,
) -> Self {
let cx = cx.create_child();
let confirmed = cx.create_rw_signal(true);
let [left, right] = [&self.left, &self.right].map(|editor_data| {
editors
.make_copy(
editor_data.id(),
cx,
None,
Some((editor_tab_id, diff_editor_id)),
Some(confirmed),
)
.unwrap()
});
let diff_editor = DiffEditorData {
scope: cx,
id: diff_editor_id,
editor_tab_id,
editor_tab_id: cx.create_rw_signal(editor_tab_id),
focus_right: cx.create_rw_signal(true),
left: cx.create_rw_signal(self.left.get_untracked().copy(
cx,
None,
EditorId::next(),
)),
right: cx.create_rw_signal(self.right.get_untracked().copy(
cx,
None,
EditorId::next(),
)),
left,
right,
confirmed,
};
diff_editor.listen_diff_changes();
@@ -218,32 +229,38 @@ impl DiffEditorData {
fn listen_diff_changes(&self) {
let cx = self.scope;
let left = self.left;
let left_doc_rev = cx.create_memo(move |_| {
let left_doc = left.with(|editor| editor.view.doc);
left_doc.with(|doc| (doc.content.clone(), doc.rev()))
});
let left = self.left.clone();
let left_doc_rev = {
let left = left.clone();
cx.create_memo(move |_| {
let doc = left.doc_signal().get();
(doc.content.get(), doc.buffer.with(|b| b.rev()))
})
};
let right = self.right;
let right_doc_rev = cx.create_memo(move |_| {
let right_doc = right.with(|editor| editor.view.doc);
right_doc.with(|doc| (doc.content.clone(), doc.rev()))
});
let right = self.right.clone();
let right_doc_rev = {
let right = right.clone();
cx.create_memo(move |_| {
let doc = right.doc_signal().get();
(doc.content.get(), doc.buffer.with(|b| b.rev()))
})
};
cx.create_effect(move |_| {
let (_, left_rev) = left_doc_rev.get();
let (left_editor_view, left_doc) =
left.with_untracked(|editor| (editor.view.kind, editor.view.doc));
let (left_atomic_rev, left_rope) = left_doc.with_untracked(|doc| {
(doc.buffer().atomic_rev(), doc.buffer().text().clone())
});
let (left_editor_view, left_doc) = (left.kind, left.doc());
let (left_atomic_rev, left_rope) =
left_doc.buffer.with_untracked(|buffer| {
(buffer.atomic_rev(), buffer.text().clone())
});
let (_, right_rev) = right_doc_rev.get();
let (right_editor_view, right_doc) =
right.with_untracked(|editor| (editor.view.kind, editor.view.doc));
let (right_atomic_rev, right_rope) = right_doc.with_untracked(|doc| {
(doc.buffer().atomic_rev(), doc.buffer().text().clone())
});
let (right_editor_view, right_doc) = (right.kind, right.doc());
let (right_atomic_rev, right_rope) =
right_doc.buffer.with_untracked(|buffer| {
(buffer.atomic_rev(), buffer.text().clone())
});
let send = {
let right_atomic_rev = right_atomic_rev.clone();
@@ -296,18 +313,16 @@ struct DiffShowMoreSection {
}
pub fn diff_show_more_section_view(
left_editor: RwSignal<EditorData>,
right_editor: RwSignal<EditorData>,
left_editor: &EditorData,
right_editor: &EditorData,
) -> impl View {
let left_editor_view = left_editor.with_untracked(|editor| editor.view.kind);
let (right_editor_view, viewport, config) =
right_editor.with_untracked(|editor| {
(editor.view.kind, editor.viewport, editor.common.config)
});
let left_editor_view = left_editor.kind;
let right_editor_view = right_editor.kind;
let viewport = right_editor.viewport();
let config = right_editor.common.config;
let each_fn = move || {
let editor_view = right_editor.with(|editor| editor.view.kind);
let editor_view = editor_view.get();
let editor_view = right_editor_view.get();
if let EditorViewKind::Diff(diff_info) = editor_view {
let viewport = viewport.get();
let config = config.get_untracked();
@@ -373,184 +388,159 @@ pub fn diff_show_more_section_view(
move |section: &DiffShowMoreSection| (section.visual_line, section.lines);
let view_fn = move |section: DiffShowMoreSection| {
stack(|| {
(
wave_box().style(move || {
Style::BASE
.absolute()
.size_pct(100.0, 100.0)
.color(*config.get().get_color(LapceColor::PANEL_BACKGROUND))
stack((
wave_box().style(move |s| {
s.absolute()
.size_pct(100.0, 100.0)
.color(config.get().color(LapceColor::PANEL_BACKGROUND))
}),
label(move || format!("{} Hidden Lines", section.lines)),
label(|| "|".to_string()).style(|s| s.margin_left(10.0)),
stack((
svg(move || config.get().ui_svg(LapceIcons::FOLD)).style(move |s| {
let config = config.get();
let size = config.ui.icon_size() as f32;
s.size(size, size)
.color(config.color(LapceColor::EDITOR_FOREGROUND))
}),
label(move || format!("{} Hidden Lines", section.lines)),
label(|| "|".to_string()).style(|| Style::BASE.margin_left_px(10.0)),
stack(|| {
(
svg(move || config.get().ui_svg(LapceIcons::FOLD)).style(
move || {
let config = config.get();
let size = config.ui.icon_size() as f32;
Style::BASE.size_px(size, size).color(
*config.get_color(LapceColor::EDITOR_FOREGROUND),
)
},
),
label(|| "Expand All".to_string())
.style(|| Style::BASE.margin_left_px(6.0)),
)
})
.on_event(EventListener::PointerDown, move |_| true)
.on_click(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::All,
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::All,
true,
);
}
});
true
})
.style(|| {
Style::BASE
.margin_left_px(10.0)
.height_pct(100.0)
.items_center()
})
.hover_style(|| Style::BASE.cursor(CursorStyle::Pointer)),
label(|| "|".to_string()).style(|| Style::BASE.margin_left_px(10.0)),
stack(|| {
(
svg(move || config.get().ui_svg(LapceIcons::FOLD_UP)).style(
move || {
let config = config.get();
let size = config.ui.icon_size() as f32;
Style::BASE.size_px(size, size).color(
*config.get_color(LapceColor::EDITOR_FOREGROUND),
)
},
),
label(|| "Expand Up".to_string())
.style(|| Style::BASE.margin_left_px(6.0)),
)
})
.on_event(EventListener::PointerDown, move |_| true)
.on_click(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::Up(10),
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::Up(10),
true,
);
}
});
true
})
.style(move || {
Style::BASE
.margin_left_px(10.0)
.height_pct(100.0)
.items_center()
})
.hover_style(|| Style::BASE.cursor(CursorStyle::Pointer)),
label(|| "|".to_string()).style(|| Style::BASE.margin_left_px(10.0)),
stack(|| {
(
svg(move || config.get().ui_svg(LapceIcons::FOLD_DOWN))
.style(move || {
let config = config.get();
let size = config.ui.icon_size() as f32;
Style::BASE.size_px(size, size).color(
*config.get_color(LapceColor::EDITOR_FOREGROUND),
)
}),
label(|| "Expand Down".to_string())
.style(|| Style::BASE.margin_left_px(6.0)),
)
})
.on_event(EventListener::PointerDown, move |_| true)
.on_click(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::Down(10),
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::Down(10),
true,
);
}
});
true
})
.style(move || {
Style::BASE
.margin_left_px(10.0)
.height_pct(100.0)
.items_center()
})
.hover_style(|| Style::BASE.cursor(CursorStyle::Pointer)),
)
})
.style(move || {
label(|| "Expand All".to_string()).style(|s| s.margin_left(6.0)),
))
.on_event_stop(EventListener::PointerDown, move |_| {})
.on_click_stop(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::All,
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::All,
true,
);
}
});
})
.style(|s| {
s.margin_left(10.0)
.height_pct(100.0)
.items_center()
.hover(|s| s.cursor(CursorStyle::Pointer))
}),
label(|| "|".to_string()).style(|s| s.margin_left(10.0)),
stack((
svg(move || config.get().ui_svg(LapceIcons::FOLD_UP)).style(
move |s| {
let config = config.get();
let size = config.ui.icon_size() as f32;
s.size(size, size)
.color(config.color(LapceColor::EDITOR_FOREGROUND))
},
),
label(|| "Expand Up".to_string()).style(|s| s.margin_left(6.0)),
))
.on_event_stop(EventListener::PointerDown, move |_| {})
.on_click_stop(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::Up(10),
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::Up(10),
true,
);
}
});
})
.style(move |s| {
s.margin_left(10.0)
.height_pct(100.0)
.items_center()
.hover(|s| s.cursor(CursorStyle::Pointer))
}),
label(|| "|".to_string()).style(|s| s.margin_left(10.0)),
stack((
svg(move || config.get().ui_svg(LapceIcons::FOLD_DOWN)).style(
move |s| {
let config = config.get();
let size = config.ui.icon_size() as f32;
s.size(size, size)
.color(config.color(LapceColor::EDITOR_FOREGROUND))
},
),
label(|| "Expand Down".to_string()).style(|s| s.margin_left(6.0)),
))
.on_event_stop(EventListener::PointerDown, move |_| {})
.on_click_stop(move |_event| {
left_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.left_actual_line,
DiffExpand::Down(10),
false,
);
}
});
right_editor_view.update(|editor_view| {
if let EditorViewKind::Diff(diff_info) = editor_view {
expand_diff_lines(
&mut diff_info.changes,
section.right_actual_line,
DiffExpand::Down(10),
true,
);
}
});
})
.style(move |s| {
s.margin_left(10.0)
.height_pct(100.0)
.items_center()
.hover(|s| s.cursor(CursorStyle::Pointer))
}),
))
.style(move |s| {
let config = config.get();
Style::BASE
.absolute()
s.absolute()
.width_pct(100.0)
.height_px(config.editor.line_height() as f32)
.height(config.editor.line_height() as f32)
.justify_center()
.items_center()
.margin_top_px(
.margin_top(
(section.visual_line * config.editor.line_height()) as f32
- viewport.get().y0 as f32,
)
.hover(|s| s.cursor(CursorStyle::Default))
})
.hover_style(|| Style::BASE.cursor(CursorStyle::Default))
};
stack(move || {
(
empty().style(move || {
Style::BASE.height_px(config.get().editor.line_height() as f32 + 1.0)
}),
clip(|| {
list(each_fn, key_fn, view_fn)
.style(|| Style::BASE.flex_col().size_pct(100.0, 100.0))
})
.style(|| Style::BASE.size_pct(100.0, 100.0)),
stack((
empty().style(move |s| {
s.height(config.get().editor.line_height() as f32 + 1.0)
}),
clip(
dyn_stack(each_fn, key_fn, view_fn)
.style(|s| s.flex_col().size_pct(100.0, 100.0)),
)
})
.style(|| Style::BASE.absolute().flex_col().size_pct(100.0, 100.0))
.style(|s| s.size_pct(100.0, 100.0)),
))
.style(|s| s.absolute().flex_col().size_pct(100.0, 100.0))
}
+102 -121
View File
@@ -1,30 +1,22 @@
use floem::{
context::PaintCx,
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout},
id::Id,
peniko::kurbo::{Point, Rect, Size},
reactive::RwSignal,
view::{ChangeFlags, View},
Renderer, ViewContext,
Renderer, View, ViewId,
};
use lapce_core::{buffer::rope_text::RopeText, mode::Mode};
use crate::{
config::{color::LapceColor, LapceConfig},
doc::Document,
};
use super::{view::changes_colors, EditorData};
use super::{view::changes_colors_screen, EditorData};
use crate::config::{color::LapceColor, LapceConfig};
pub struct EditorGutterView {
id: Id,
editor: RwSignal<EditorData>,
id: ViewId,
editor: EditorData,
width: f64,
}
pub fn editor_gutter_view(editor: RwSignal<EditorData>) -> EditorGutterView {
let cx = ViewContext::get_current();
let id = cx.new_id();
pub fn editor_gutter_view(editor: EditorData) -> EditorGutterView {
let id = ViewId::new();
EditorGutterView {
id,
@@ -37,7 +29,7 @@ impl EditorGutterView {
fn paint_head_changes(
&self,
cx: &mut PaintCx,
doc: RwSignal<Document>,
e_data: &EditorData,
viewport: Rect,
is_normal: bool,
config: &LapceConfig,
@@ -46,21 +38,17 @@ impl EditorGutterView {
return;
}
let changes = doc.with_untracked(|doc| doc.head_changes);
let changes = changes.get();
let changes = e_data.doc().head_changes().get_untracked();
let line_height = config.editor.line_height() as f64;
let min_line = (viewport.y0 / line_height).floor() as usize;
let max_line = (viewport.y1 / line_height).ceil() as usize;
let changes = changes_colors(changes, min_line, max_line, config);
let changes = changes_colors_screen(config, &e_data.editor, changes);
for (y, height, removed, color) in changes {
let height = if removed {
10.0
} else {
height as f64 * line_height
};
let mut y = y as f64 * line_height - viewport.y0;
let mut y = y - viewport.y0;
if removed {
y -= 5.0;
}
@@ -69,140 +57,133 @@ impl EditorGutterView {
.to_rect()
.with_origin(Point::new(self.width + 7.0, y)),
color,
0.0,
)
}
}
fn paint_sticky_headers(
&self,
cx: &mut PaintCx,
is_normal: bool,
config: &LapceConfig,
) {
if !is_normal {
return;
}
if !config.editor.sticky_header {
return;
}
let sticky_header_height = self.editor.sticky_header_height;
let sticky_header_height = sticky_header_height.get_untracked();
if sticky_header_height == 0.0 {
return;
}
let sticky_area_rect =
Size::new(self.width + 25.0 + 30.0, sticky_header_height)
.to_rect()
.with_origin(Point::new(-25.0, 0.0))
.inflate(25.0, 0.0);
cx.fill(
&sticky_area_rect,
config.color(LapceColor::LAPCE_DROPDOWN_SHADOW),
3.0,
);
cx.fill(
&sticky_area_rect,
config.color(LapceColor::EDITOR_STICKY_HEADER_BACKGROUND),
0.0,
);
}
}
impl View for EditorGutterView {
fn id(&self) -> Id {
fn id(&self) -> ViewId {
self.id
}
fn child(&self, _id: Id) -> Option<&dyn View> {
None
}
fn child_mut(&mut self, _id: Id) -> Option<&mut dyn View> {
None
}
fn children(&self) -> Vec<&dyn View> {
Vec::new()
}
fn children_mut(&mut self) -> Vec<&mut dyn View> {
Vec::new()
}
fn update(
&mut self,
_cx: &mut floem::context::UpdateCx,
_state: Box<dyn std::any::Any>,
) -> ChangeFlags {
ChangeFlags::default()
}
fn layout(
&mut self,
cx: &mut floem::context::LayoutCx,
) -> floem::taffy::prelude::Node {
cx.layout_node(self.id, false, |_| Vec::new())
}
fn compute_layout(
&mut self,
cx: &mut floem::context::LayoutCx,
_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
}
fn event(
&mut self,
_cx: &mut floem::context::EventCx,
_id_path: Option<&[Id]>,
_event: floem::event::Event,
) -> bool {
false
}
fn paint(&mut self, cx: &mut floem::context::PaintCx) {
let (view, cursor, viewport, screen_lines, config) =
self.editor.with_untracked(|editor| {
(
editor.view.clone(),
editor.cursor,
editor.viewport,
editor.screen_lines(),
editor.common.config,
)
});
let viewport = viewport.get_untracked();
let viewport = self.editor.viewport().get_untracked();
let cursor = self.editor.cursor();
let screen_lines = self.editor.screen_lines();
let config = self.editor.common.config;
let kind_is_normal = view.kind.with_untracked(|kind| kind.is_normal());
let kind_is_normal =
self.editor.kind.with_untracked(|kind| kind.is_normal());
let (offset, mode) = cursor.with_untracked(|c| (c.offset(), c.get_mode()));
let config = config.get_untracked();
let line_height = config.editor.line_height() as f64;
let last_line = view.last_line();
let current_line = view
.doc
.with_untracked(|doc| doc.buffer().line_of_offset(offset));
let last_line = self.editor.editor.last_line();
let current_line = self
.editor
.doc()
.buffer
.with_untracked(|buffer| buffer.line_of_offset(offset));
let family: Vec<FamilyOwned> =
FamilyOwned::parse_list(&config.editor.font_family).collect();
let attrs = Attrs::new()
.family(&family)
.color(*config.get_color(LapceColor::EDITOR_DIM))
.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.get_color(LapceColor::EDITOR_FOREGROUND)),
);
let current_line_attrs_list =
AttrsList::new(attrs.color(config.color(LapceColor::EDITOR_FOREGROUND)));
let show_relative = config.core.modal
&& config.editor.modal_mode_relative_line_numbers
&& mode != Mode::Insert
&& kind_is_normal;
for line in &screen_lines.lines {
let line = *line;
if line > last_line {
break;
}
let text = if show_relative {
if line == current_line {
line + 1
} else {
line.abs_diff(current_line)
screen_lines.with_untracked(|screen_lines| {
for (line, y) in screen_lines.iter_lines_y() {
// If it ends up outside the bounds of the file, stop trying to display line numbers
if line > last_line {
break;
}
} else {
line + 1
let text = if show_relative {
if line == current_line {
line + 1
} else {
line.abs_diff(current_line)
}
} else {
line + 1
}
.to_string();
let mut text_layout = TextLayout::new();
if line == current_line {
text_layout.set_text(&text, current_line_attrs_list.clone());
} else {
text_layout.set_text(&text, attrs_list.clone());
}
let size = text_layout.size();
let height = size.height;
cx.draw_text(
&text_layout,
Point::new(
(self.width - (size.width)).max(0.0),
y + (line_height - height) / 2.0 - viewport.y0,
),
);
}
.to_string();
});
let info = screen_lines.info.get(&line).unwrap();
let mut text_layout = TextLayout::new();
if line == current_line {
text_layout.set_text(&text, current_line_attrs_list.clone());
} else {
text_layout.set_text(&text, attrs_list.clone());
}
let size = text_layout.size();
let height = size.height;
let y = info.y;
cx.draw_text(
&text_layout,
Point::new(
(self.width - (size.width)).max(0.0),
y as f64 + (line_height - height) / 2.0 - viewport.y0,
),
);
}
self.paint_head_changes(cx, view.doc, viewport, kind_is_normal, &config);
self.paint_head_changes(cx, &self.editor, viewport, kind_is_normal, &config);
self.paint_sticky_headers(cx, kind_is_normal, &config);
}
}
+4 -6
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use floem::peniko::kurbo::Vec2;
use lapce_core::buffer::{rope_text::RopeText, Buffer};
use lapce_core::{buffer::rope_text::RopeText, rope_text_pos::RopeTextPosition};
use lsp_types::Position;
#[derive(Clone, Debug, PartialEq)]
@@ -24,12 +24,10 @@ pub enum EditorPosition {
}
impl EditorPosition {
pub fn to_offset(&self, buffer: &Buffer) -> usize {
pub fn to_offset(&self, text: &impl RopeText) -> usize {
match self {
EditorPosition::Line(n) => buffer.first_non_blank_character_on_line(*n),
EditorPosition::Position(position) => {
buffer.offset_of_position(position)
}
EditorPosition::Line(n) => text.first_non_blank_character_on_line(*n),
EditorPosition::Position(position) => text.offset_of_position(position),
EditorPosition::Offset(offset) => *offset,
}
}
-734
View File
@@ -1,734 +0,0 @@
//! Movement logic for the editor.
use std::collections::HashSet;
use lapce_core::{
buffer::rope_text::RopeText,
command::MultiSelectionCommand,
cursor::{ColPosition, Cursor, CursorMode},
editor::Editor,
mode::{Mode, MotionMode},
movement::{LinePosition, Movement},
register::Register,
selection::{SelRegion, Selection},
soft_tab::{snap_to_soft_tab, SnapDirection},
};
use crate::doc::Document;
use super::view_data::EditorViewData;
/// Move a selection region by a given movement.
/// Much of the time, this will just be a matter of moving the cursor, but
/// some movements may depend on the current selection.
fn move_region(
view: &EditorViewData,
region: &SelRegion,
count: usize,
modify: bool,
movement: &Movement,
mode: Mode,
) -> SelRegion {
let (count, region) = if count >= 1 && !modify && !region.is_caret() {
// If we're not a caret, and we are moving left/up or right/down, we want to move
// the cursor to the left or right side of the selection.
// Ex: `|abc|` -> left/up arrow key -> `|abc`
// Ex: `|abc|` -> right/down arrow key -> `abc|`
// and it doesn't matter which direction the selection is going, so we use min/max
match movement {
Movement::Left | Movement::Up => {
let leftmost = region.min();
(count - 1, SelRegion::new(leftmost, leftmost, region.horiz))
}
Movement::Right | Movement::Down => {
let rightmost = region.max();
(
count - 1,
SelRegion::new(rightmost, rightmost, region.horiz),
)
}
_ => (count, *region),
}
} else {
(count, *region)
};
let (end, horiz) = move_offset(
view,
region.end,
region.horiz.as_ref(),
count,
movement,
mode,
);
let start = match modify {
true => region.start,
false => end,
};
SelRegion::new(start, end, horiz)
}
pub fn move_selection(
view: &EditorViewData,
selection: &Selection,
count: usize,
modify: bool,
movement: &Movement,
mode: Mode,
) -> Selection {
let mut new_selection = Selection::new();
for region in selection.regions() {
new_selection
.add_region(move_region(view, region, count, modify, movement, mode));
}
new_selection
}
pub fn move_offset(
view: &EditorViewData,
offset: usize,
horiz: Option<&ColPosition>,
count: usize,
movement: &Movement,
mode: Mode,
) -> (usize, Option<ColPosition>) {
let config = view.config.get_untracked();
match movement {
Movement::Left => {
let new_offset = move_left(
view.rope_text(),
offset,
mode,
count,
config.editor.atomic_soft_tab_width(),
);
(new_offset, None)
}
Movement::Right => {
let new_offset = move_right(
view.rope_text(),
offset,
mode,
count,
config.editor.atomic_soft_tab_width(),
);
(new_offset, None)
}
Movement::Up => {
let font_size = config.editor.font_size();
let (new_offset, horiz) =
move_up(view, font_size, offset, horiz.cloned(), mode, count);
(new_offset, Some(horiz))
}
Movement::Down => {
let font_size = config.editor.font_size();
let (new_offset, horiz) =
move_down(view, font_size, offset, horiz.cloned(), mode, count);
(new_offset, Some(horiz))
}
Movement::DocumentStart => (0, Some(ColPosition::Start)),
Movement::DocumentEnd => {
let (new_offset, horiz) = document_end(view.rope_text(), mode);
(new_offset, Some(horiz))
}
Movement::FirstNonBlank => {
let (new_offset, horiz) = first_non_blank(view.rope_text(), offset);
(new_offset, Some(horiz))
}
Movement::StartOfLine => {
let (new_offset, horiz) = start_of_line(view.rope_text(), offset);
(new_offset, Some(horiz))
}
Movement::EndOfLine => {
let (new_offset, horiz) = end_of_line(view.rope_text(), offset, mode);
(new_offset, Some(horiz))
}
Movement::Line(position) => {
let font_size = config.editor.font_size();
let (new_offset, horiz) =
to_line(view, font_size, offset, horiz.cloned(), mode, position);
(new_offset, Some(horiz))
}
Movement::Offset(offset) => {
let new_offset = view.text().prev_grapheme_offset(*offset + 1).unwrap();
(new_offset, None)
}
Movement::WordEndForward => {
let new_offset = view.rope_text().move_n_wordends_forward(
offset,
count,
mode == Mode::Insert,
);
(new_offset, None)
}
Movement::WordForward => {
let new_offset = view.rope_text().move_n_words_forward(offset, count);
(new_offset, None)
}
Movement::WordBackward => {
let new_offset =
view.rope_text().move_n_words_backward(offset, count, mode);
(new_offset, None)
}
Movement::NextUnmatched(char) => {
let new_offset = view.find_unmatched(offset, false, *char);
(new_offset, None)
}
Movement::PreviousUnmatched(char) => {
let new_offset = view.find_unmatched(offset, true, *char);
(new_offset, None)
}
Movement::MatchPairs => {
let new_offset = view.find_matching_pair(offset);
(new_offset, None)
}
Movement::ParagraphForward => {
let new_offset =
view.rope_text().move_n_paragraphs_forward(offset, count);
(new_offset, None)
}
Movement::ParagraphBackward => {
let new_offset =
view.rope_text().move_n_paragraphs_backward(offset, count);
(new_offset, None)
}
}
}
/// Move the offset to the left by `count` amount.
/// If `soft_tab_width` is `Some` (and greater than 1) then the offset will snap to the soft tab.
fn move_left(
rope_text: impl RopeText,
offset: usize,
mode: Mode,
count: usize,
soft_tab_width: Option<usize>,
) -> usize {
let mut new_offset = rope_text.move_left(offset, mode, count);
if let Some(soft_tab_width) = soft_tab_width {
if soft_tab_width > 1 {
new_offset = snap_to_soft_tab(
rope_text.text(),
new_offset,
SnapDirection::Left,
soft_tab_width,
);
}
}
new_offset
}
/// Move the offset to the right by `count` amount.
/// If `soft_tab_width` is `Some` (and greater than 1) then the offset will snap to the soft tab.
fn move_right(
rope_text: impl RopeText,
offset: usize,
mode: Mode,
count: usize,
soft_tab_width: Option<usize>,
) -> usize {
let mut new_offset = rope_text.move_right(offset, mode, count);
if let Some(soft_tab_width) = soft_tab_width {
if soft_tab_width > 1 {
new_offset = snap_to_soft_tab(
rope_text.text(),
new_offset,
SnapDirection::Right,
soft_tab_width,
);
}
}
new_offset
}
// These could be abstracted away from `EditorViewData` by having some trait that has a 'get Rope' and 'get point information about text' functions. Would be useful for testing.
/// Move the offset up by `count` amount.
fn move_up(
view: &EditorViewData,
font_size: usize,
offset: usize,
horiz: Option<ColPosition>,
mode: Mode,
count: usize,
) -> (usize, ColPosition) {
let rope_text = view.rope_text();
let line = rope_text.line_of_offset(offset);
if line == 0 {
let line = rope_text.line_of_offset(offset);
let new_offset = rope_text.offset_of_line(line);
let horiz = horiz.unwrap_or_else(|| {
ColPosition::Col(view.line_point_of_offset(offset, font_size).x)
});
return (new_offset, horiz);
}
let visual_line = view.visual_line(line).saturating_sub(count);
let line = view.actual_line(visual_line, false);
let horiz = horiz.unwrap_or_else(|| {
ColPosition::Col(view.line_point_of_offset(offset, font_size).x)
});
let col = view.line_horiz_col(line, font_size, &horiz, mode != Mode::Normal);
let new_offset = rope_text.offset_of_line_col(line, col);
(new_offset, horiz)
}
/// Move the offset down by `count` amount.
fn move_down(
view: &EditorViewData,
font_size: usize,
offset: usize,
horiz: Option<ColPosition>,
mode: Mode,
count: usize,
) -> (usize, ColPosition) {
let rope_text = view.rope_text();
let last_line = rope_text.last_line();
let line = rope_text.line_of_offset(offset);
if line == last_line {
let new_offset = rope_text.offset_line_end(offset, mode != Mode::Normal);
let horiz = horiz.unwrap_or_else(|| {
ColPosition::Col(view.line_point_of_offset(offset, font_size).x)
});
return (new_offset, horiz);
}
let visual_line = view.visual_line(line);
let line = view.actual_line(visual_line + count, true);
let line = line.min(last_line);
let horiz = horiz.unwrap_or_else(|| {
ColPosition::Col(view.line_point_of_offset(offset, font_size).x)
});
let col = view.line_horiz_col(line, font_size, &horiz, mode != Mode::Normal);
let new_offset = rope_text.offset_of_line_col(line, col);
(new_offset, horiz)
}
fn document_end(rope_text: impl RopeText, mode: Mode) -> (usize, ColPosition) {
let last_offset =
rope_text.offset_line_end(rope_text.len(), mode != Mode::Normal);
(last_offset, ColPosition::End)
}
fn first_non_blank(rope_text: impl RopeText, offset: usize) -> (usize, ColPosition) {
let line = rope_text.line_of_offset(offset);
let non_blank_offset = rope_text.first_non_blank_character_on_line(line);
let start_line_offset = rope_text.offset_of_line(line);
if offset > non_blank_offset {
// Jump to the first non-whitespace character if we're strictly after it
(non_blank_offset, ColPosition::FirstNonBlank)
} else {
// If we're at the start of the line, also jump to the first not blank
if start_line_offset == offset {
(non_blank_offset, ColPosition::FirstNonBlank)
} else {
// Otherwise, jump to the start of the line
(start_line_offset, ColPosition::Start)
}
}
}
fn start_of_line(rope_text: impl RopeText, offset: usize) -> (usize, ColPosition) {
let line = rope_text.line_of_offset(offset);
let new_offset = rope_text.offset_of_line(line);
(new_offset, ColPosition::Start)
}
fn end_of_line(
rope_text: impl RopeText,
offset: usize,
mode: Mode,
) -> (usize, ColPosition) {
let new_offset = rope_text.offset_line_end(offset, mode != Mode::Normal);
(new_offset, ColPosition::End)
}
fn to_line(
view: &EditorViewData,
font_size: usize,
offset: usize,
horiz: Option<ColPosition>,
mode: Mode,
position: &LinePosition,
) -> (usize, ColPosition) {
let rope_text = view.rope_text();
let line = match position {
LinePosition::Line(line) => (line - 1).min(rope_text.last_line()),
LinePosition::First => 0,
LinePosition::Last => rope_text.last_line(),
};
let horiz = horiz.unwrap_or_else(|| {
ColPosition::Col(view.line_point_of_offset(offset, font_size).x)
});
let col = view.line_horiz_col(line, font_size, &horiz, mode != Mode::Normal);
let new_offset = rope_text.offset_of_line_col(line, col);
(new_offset, horiz)
}
// TODO: passing a view with the document is kinda ehhh, because this would be used when you call
// .update on the `RwSignal<Document>`, which presumably delinks it from the RwSignal the view has.
// A solution might be to change the caller to only pass in the editor view and have this do the update, but only when it is needed.
/// Move the current cursor.
/// This will signal-update the document for some motion modes.
pub fn move_cursor(
view: &EditorViewData,
cursor: &mut Cursor,
movement: &Movement,
count: usize,
modify: bool,
register: &mut Register,
) {
match cursor.mode {
CursorMode::Normal(offset) => {
let (new_offset, horiz) = move_offset(
view,
offset,
cursor.horiz.as_ref(),
count,
movement,
Mode::Normal,
);
if let Some(motion_mode) = cursor.motion_mode.clone() {
let (moved_new_offset, _) = move_offset(
view,
new_offset,
None,
1,
&Movement::Right,
Mode::Insert,
);
let (start, end) = match movement {
Movement::EndOfLine | Movement::WordEndForward => {
(offset, moved_new_offset)
}
Movement::MatchPairs => {
if new_offset > offset {
(offset, moved_new_offset)
} else {
(moved_new_offset, new_offset)
}
}
_ => (offset, new_offset),
};
view.doc.update(|doc| {
let deltas = Editor::execute_motion_mode(
cursor,
doc.buffer_mut(),
motion_mode,
start,
end,
movement.is_vertical(),
register,
);
doc.apply_deltas(&deltas);
});
cursor.motion_mode = None;
} else {
cursor.mode = CursorMode::Normal(new_offset);
cursor.horiz = horiz;
}
}
CursorMode::Visual { start, end, mode } => {
let (new_offset, horiz) = move_offset(
view,
end,
cursor.horiz.as_ref(),
count,
movement,
Mode::Visual,
);
cursor.mode = CursorMode::Visual {
start,
end: new_offset,
mode,
};
cursor.horiz = horiz;
}
CursorMode::Insert(ref selection) => {
let selection = move_selection(
view,
selection,
count,
modify,
movement,
Mode::Insert,
);
cursor.set_insert(selection);
}
}
}
pub fn do_multi_selection(
view: &EditorViewData,
cursor: &mut Cursor,
cmd: &MultiSelectionCommand,
) {
use MultiSelectionCommand::*;
let rope_text = view.rope_text();
match cmd {
SelectUndo => {
if let CursorMode::Insert(_) = cursor.mode.clone() {
if let Some(selection) = cursor.history_selections.last().cloned() {
cursor.mode = CursorMode::Insert(selection);
}
cursor.history_selections.pop();
}
}
InsertCursorAbove => {
if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
let offset = selection.first().map(|s| s.end).unwrap_or(0);
let (new_offset, _) = move_offset(
view,
offset,
cursor.horiz.as_ref(),
1,
&Movement::Up,
Mode::Insert,
);
if new_offset != offset {
selection
.add_region(SelRegion::new(new_offset, new_offset, None));
}
cursor.set_insert(selection);
}
}
InsertCursorBelow => {
if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
let offset = selection.last().map(|s| s.end).unwrap_or(0);
let (new_offset, _) = move_offset(
view,
offset,
cursor.horiz.as_ref(),
1,
&Movement::Down,
Mode::Insert,
);
if new_offset != offset {
selection
.add_region(SelRegion::new(new_offset, new_offset, None));
}
cursor.set_insert(selection);
}
}
InsertCursorEndOfLine => {
if let CursorMode::Insert(selection) = cursor.mode.clone() {
let mut new_selection = Selection::new();
for region in selection.regions() {
let (start_line, _) = rope_text.offset_to_line_col(region.min());
let (end_line, end_col) =
rope_text.offset_to_line_col(region.max());
for line in start_line..end_line + 1 {
let offset = if line == end_line {
rope_text.offset_of_line_col(line, end_col)
} else {
rope_text.line_end_offset(line, true)
};
new_selection
.add_region(SelRegion::new(offset, offset, None));
}
}
cursor.set_insert(new_selection);
}
}
SelectCurrentLine => {
if let CursorMode::Insert(selection) = cursor.mode.clone() {
let mut new_selection = Selection::new();
for region in selection.regions() {
let start_line = rope_text.line_of_offset(region.min());
let start = rope_text.offset_of_line(start_line);
let end_line = rope_text.line_of_offset(region.max());
let end = rope_text.offset_of_line(end_line + 1);
new_selection.add_region(SelRegion::new(start, end, None));
}
cursor.set_insert(new_selection);
}
}
SelectAllCurrent => {
if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
if !selection.is_empty() {
let find = view.find();
let config = view.config.get_untracked();
let first = selection.first().unwrap();
let (start, end) = if first.is_caret() {
rope_text.select_word(first.start)
} else {
(first.min(), first.max())
};
let search_str = rope_text.slice_to_cow(start..end);
let case_sensitive = find.case_sensitive(false);
let multicursor_case_sensitive =
config.editor.multicursor_case_sensitive;
let case_sensitive =
multicursor_case_sensitive || case_sensitive;
// let search_whole_word = config.editor.multicursor_whole_words;
find.set_case_sensitive(case_sensitive);
find.set_find(&search_str);
let mut offset = 0;
while let Some((start, end)) =
find.next(rope_text.text(), offset, false, false)
{
offset = end;
selection.add_region(SelRegion::new(start, end, None));
}
}
cursor.set_insert(selection);
}
}
SelectNextCurrent => {
if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
if !selection.is_empty() {
let mut had_caret = false;
for region in selection.regions_mut() {
if region.is_caret() {
had_caret = true;
let (start, end) = rope_text.select_word(region.start);
region.start = start;
region.end = end;
}
}
if !had_caret {
let find = view.find();
let config = view.config.get_untracked();
let r = selection.last_inserted().unwrap();
let search_str = rope_text.slice_to_cow(r.min()..r.max());
let case_sensitive = find.case_sensitive(false);
let case_sensitive =
config.editor.multicursor_case_sensitive
|| case_sensitive;
// let search_whole_word =
// config.editor.multicursor_whole_words;
find.set_case_sensitive(case_sensitive);
find.set_find(&search_str);
let mut offset = r.max();
let mut seen = HashSet::new();
while let Some((start, end)) =
find.next(rope_text.text(), offset, false, true)
{
if !selection
.regions()
.iter()
.any(|r| r.min() == start && r.max() == end)
{
selection
.add_region(SelRegion::new(start, end, None));
break;
}
if seen.contains(&end) {
break;
}
offset = end;
seen.insert(offset);
}
}
}
cursor.set_insert(selection);
}
}
SelectSkipCurrent => {
if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
if !selection.is_empty() {
let r = selection.last_inserted().unwrap();
if r.is_caret() {
let (start, end) = rope_text.select_word(r.start);
selection.replace_last_inserted_region(SelRegion::new(
start, end, None,
));
} else {
let find = view.find();
let search_str = rope_text.slice_to_cow(r.min()..r.max());
find.set_find(&search_str);
let mut offset = r.max();
let mut seen = HashSet::new();
while let Some((start, end)) =
find.next(rope_text.text(), offset, false, true)
{
if !selection
.regions()
.iter()
.any(|r| r.min() == start && r.max() == end)
{
selection.replace_last_inserted_region(
SelRegion::new(start, end, None),
);
break;
}
if seen.contains(&end) {
break;
}
offset = end;
seen.insert(offset);
}
}
}
cursor.set_insert(selection);
}
}
SelectAll => {
let new_selection = Selection::region(0, rope_text.len());
cursor.set_insert(new_selection);
}
}
}
pub fn do_motion_mode(
doc: &mut Document,
cursor: &mut Cursor,
motion_mode: MotionMode,
register: &mut Register,
) {
if let Some(m) = &cursor.motion_mode {
if m == &motion_mode {
let offset = cursor.offset();
let deltas = Editor::execute_motion_mode(
cursor,
doc.buffer_mut(),
motion_mode,
offset,
offset,
true,
register,
);
doc.apply_deltas(&deltas);
}
cursor.motion_mode = None;
} else {
cursor.motion_mode = Some(motion_mode);
}
}
// TODO: Write tests for the various functions.
+1648 -1271
View File
File diff suppressed because it is too large Load Diff
-747
View File
@@ -1,747 +0,0 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use floem::{
cosmic_text::TextLayout,
peniko::{kurbo::Point, Color},
reactive::{ReadSignal, RwSignal, Scope},
views::VirtualListVector,
};
use lapce_core::{
buffer::{
diff::DiffLines,
rope_text::{RopeText, RopeTextVal},
},
char_buffer::CharBuffer,
cursor::ColPosition,
mode::Mode,
soft_tab::{snap_to_soft_tab_line_col, SnapDirection},
word::WordCursor,
};
use lapce_xi_rope::Rope;
use crate::{
config::LapceConfig,
doc::{phantom_text::PhantomTextLine, Document},
find::{Find, FindResult},
};
use super::diff::DiffInfo;
#[derive(Clone)]
pub struct LineExtraStyle {
pub x: f64,
pub width: Option<f64>,
pub bg_color: Option<Color>,
pub under_line: Option<Color>,
pub wave_line: Option<Color>,
}
#[derive(Clone)]
pub struct TextLayoutLine {
/// Extra styling that should be applied to the text
/// (x0, x1 or line display end, style)
pub extra_style: Vec<LineExtraStyle>,
pub text: TextLayout,
pub whitespaces: Option<Vec<(char, (f64, f64))>>,
pub indent: f64,
}
/// Keeps track of the text layouts so that we can efficiently reuse them.
#[derive(Clone, Default)]
pub struct TextLayoutCache {
/// The id of the last config, which lets us know when the config changes so we can update
/// the cache.
config_id: u64,
cache_rev: u64,
/// (Font Size -> (Line Number -> Text Layout))
/// Different font-sizes are cached separately, which is useful for features like code lens
/// where the text becomes small but you may wish to revert quickly.
pub layouts: HashMap<usize, HashMap<usize, Arc<TextLayoutLine>>>,
pub max_width: f64,
}
impl TextLayoutCache {
pub fn new() -> Self {
Self {
config_id: 0,
cache_rev: 0,
layouts: HashMap::new(),
max_width: 0.0,
}
}
pub fn clear(&mut self, cache_rev: u64) {
self.layouts.clear();
self.cache_rev = cache_rev;
self.max_width = 0.0;
}
pub fn check_attributes(&mut self, config_id: u64) {
if self.config_id != config_id {
self.clear(self.cache_rev + 1);
self.config_id = config_id;
}
}
}
pub struct DocLine {
pub rev: u64,
pub style_rev: u64,
pub line: usize,
pub text: Arc<TextLayoutLine>,
}
#[derive(Clone)]
pub enum EditorViewKind {
Normal,
Diff(DiffInfo),
}
impl EditorViewKind {
pub fn is_normal(&self) -> bool {
matches!(self, EditorViewKind::Normal)
}
}
// TODO(minor): Should this go in another file? It doesn't really need to be with the drawing code for editor views
/// Data specific to the rendering of a view.
/// This has various helper methods that may dispatch to the held [`Document`] signal, but are
/// untracked by default. If you need your signal to depend on the document,
/// then you should call [`EditorViewData::track_doc`].
/// Note: This should be cheap to clone.
#[derive(Clone)]
pub struct EditorViewData {
/// Equivalent to the `EditorData::doc` that contains this view.
pub doc: RwSignal<Document>,
pub kind: RwSignal<EditorViewKind>,
/// The text layouts for the document. This may be shared with other views.
pub text_layouts: Rc<RefCell<TextLayoutCache>>,
pub config: ReadSignal<Arc<LapceConfig>>,
}
impl VirtualListVector<DocLine> for EditorViewData {
type ItemIterator = std::vec::IntoIter<DocLine>;
fn total_len(&self) -> usize {
self.num_lines()
}
fn slice(&mut self, range: std::ops::Range<usize>) -> Self::ItemIterator {
let lines = range
.into_iter()
.map(|line| DocLine {
rev: self.rev(),
style_rev: self.style_rev(),
line,
text: self.get_text_layout(line, 12),
})
.collect::<Vec<_>>();
lines.into_iter()
}
}
impl EditorViewData {
pub fn new(
doc: RwSignal<Document>,
kind: RwSignal<EditorViewKind>,
config: ReadSignal<Arc<LapceConfig>>,
) -> EditorViewData {
EditorViewData {
doc,
kind,
text_layouts: Rc::new(RefCell::new(TextLayoutCache::new())),
config,
}
}
// Note: There is no document / buffer function as that would require cloning it
// since we can't get a reference out of the RwSignal document.
/// Subscribe to the doc signal, since the getter functions use `with_untracked` by default.
pub fn track_doc(&self) {
self.doc.track();
}
/// Return the underlying `Rope` of the document.
pub fn text(&self) -> Rope {
self.doc.with_untracked(|doc| doc.buffer().text().clone())
}
/// Return a [`RopeTextVal`] wrapper.
/// Unfortunately, we can't implement [`RopeText`] directly on [`EditorViewData`] due to
/// it not having a reference to the rope.
pub fn rope_text(&self) -> RopeTextVal {
RopeTextVal::new(self.text())
}
/// Return the [`Document`]'s [`Find`] instance. Find uses signals, and so can be updated.
pub fn find(&self) -> Find {
self.doc.with_untracked(|doc| doc.find().clone())
}
pub fn find_result(&self) -> FindResult {
self.doc.with_untracked(|doc| doc.find_result.clone())
}
pub fn update_find(&self, min_line: usize, max_line: usize) {
self.doc
.with_untracked(|doc| doc.update_find(min_line, max_line));
}
/// The current revision of the underlying buffer. This is used to track when the buffer has
/// changed.
pub fn rev(&self) -> u64 {
self.doc.with_untracked(|doc| doc.rev())
}
// TODO: Should the editor view handle the style information?
// It does not need to, since we can just get that information from the document,
// but it might be nicer? It would let us make the document more strictly about the
// data and the view more about the rendering.
// but you could also consider it as the document managing the syntax it should apply
// Though. There is the question of whether we'd want to allow different syntax highlighting in
// different views. However, we probably wouldn't.
pub fn style_rev(&self) -> u64 {
self.doc.with_untracked(|doc| doc.cache_rev())
}
/// The document for the given view was swapped out.
pub fn update_doc(&mut self, doc: RwSignal<Document>) {
self.doc = doc;
self.text_layouts.borrow_mut().clear(0);
}
/// Duplicate as a new view which refers to the same document.
pub fn duplicate(&self, cx: Scope) -> Self {
// TODO: This is correct right now, as it has the views share the same text layout cache.
// However, once we have line wrapping or other view-specific rendering changes, this should check for whether they're different.
// This will likely require more information to be passed into duplicate,
// like whether the wrap width will be the editor's width, and so it is unlikely to be exactly the same as the current view.
EditorViewData {
doc: self.doc,
text_layouts: Rc::new(RefCell::new(TextLayoutCache::new())),
kind: cx.create_rw_signal(self.kind.get_untracked()),
config: self.config,
}
}
pub fn line_phantom_text(&self, line: usize) -> PhantomTextLine {
self.doc.with_untracked(|doc| doc.line_phantom_text(line))
}
/// Get the text layout for the given line.
/// If the text layout is not cached, it will be created and cached.
pub fn get_text_layout(
&self,
line: usize,
font_size: usize,
) -> Arc<TextLayoutLine> {
{
let mut text_layouts = self.text_layouts.borrow_mut();
self.doc.with_untracked(|doc| {
if doc.cache_rev() != text_layouts.cache_rev {
text_layouts.clear(doc.cache_rev());
}
})
}
// TODO: Should we just move the config cache check into `check_cache`?
let config = self.config.get_untracked();
// Check if the text layout needs to update due to the config being changed
self.text_layouts.borrow_mut().check_attributes(config.id);
// If we don't have a second layer of the hashmap initialized for this specific font size,
// do it now
if self.text_layouts.borrow().layouts.get(&font_size).is_none() {
let mut cache = self.text_layouts.borrow_mut();
cache.layouts.insert(font_size, HashMap::new());
}
// Get whether there's an entry for this specific font size and line
let cache_exists = self
.text_layouts
.borrow()
.layouts
.get(&font_size)
.unwrap()
.get(&line)
.is_some();
// If there isn't an entry then we actually have to create it
if !cache_exists {
let text_layout = self
.doc
.with_untracked(|doc| doc.get_text_layout(line, font_size));
let mut cache = self.text_layouts.borrow_mut();
let width = text_layout.text.size().width;
if width > cache.max_width {
cache.max_width = width;
}
cache
.layouts
.get_mut(&font_size)
.unwrap()
.insert(line, text_layout);
}
// Just get the entry, assuming it has been created because we initialize it above.
self.text_layouts
.borrow()
.layouts
.get(&font_size)
.unwrap()
.get(&line)
.cloned()
.unwrap()
}
pub fn indent_unit(&self) -> &'static str {
self.doc.with_untracked(|doc| doc.buffer().indent_unit())
}
// ==== Position Information ====
/// The number of visual lines in the document.
pub fn num_lines(&self) -> usize {
self.doc.with_untracked(|doc| doc.buffer().num_lines())
}
/// The last allowed line in the document.
pub fn last_line(&self) -> usize {
self.doc.with_untracked(|doc| doc.buffer().last_line())
}
// ==== Line/Column Positioning ====
/// Convert an offset into the buffer into a line and column.
pub fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
self.doc
.with_untracked(|doc| doc.buffer().offset_to_line_col(offset))
}
pub fn offset_of_line(&self, offset: usize) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().offset_of_line(offset))
}
pub fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().offset_of_line_col(line, col))
}
pub fn line_of_offset(&self, offset: usize) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().line_of_offset(offset))
}
/// Returns the offset into the buffer of the first non blank character on the given line.
pub fn first_non_blank_character_on_line(&self, line: usize) -> usize {
self.doc.with_untracked(|doc| {
doc.buffer().first_non_blank_character_on_line(line)
})
}
pub fn line_end_col(&self, line: usize, caret: bool) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().line_end_col(line, caret))
}
pub fn select_word(&self, offset: usize) -> (usize, usize) {
self.doc
.with_untracked(|doc| doc.buffer().select_word(offset))
}
// ==== Points of locations ====
/// Returns the point into the text layout of the line at the given offset.
/// `x` being the leading edge of the character, and `y` being the baseline.
pub fn line_point_of_offset(&self, offset: usize, font_size: usize) -> Point {
let (line, col) = self.offset_to_line_col(offset);
self.line_point_of_line_col(line, col, font_size)
}
/// Returns the point into the text layout of the line at the given line and column.
/// `x` being the leading edge of the character, and `y` being the baseline.
pub fn line_point_of_line_col(
&self,
line: usize,
col: usize,
font_size: usize,
) -> Point {
let text_layout = self.get_text_layout(line, font_size);
text_layout.text.hit_position(col).point
}
/// Get the (point above, point below) of a particular offset within the editor.
pub fn points_of_offset(&self, offset: usize) -> (Point, Point) {
let (line, col) = self.offset_to_line_col(offset);
self.points_of_line_col(line, col)
}
/// Get the (point above, point below) of a particular (line, col) within the editor.
pub fn points_of_line_col(&self, line: usize, col: usize) -> (Point, Point) {
let config = self.config.get_untracked();
let (line_height, font_size) =
(config.editor.line_height(), config.editor.font_size());
let y = self.visual_line(line) * line_height;
let line = line.min(self.last_line());
let phantom_text = self.line_phantom_text(line);
let col = phantom_text.col_after(col, false);
let mut x_shift = 0.0;
if font_size < config.editor.font_size() {
let mut col = 0usize;
self.doc.with_untracked(|doc| {
let line_content = doc.buffer().line_content(line);
for ch in line_content.chars() {
if ch == ' ' || ch == '\t' {
col += 1;
} else {
break;
}
}
});
if col > 0 {
let normal_text_layout =
self.get_text_layout(line, config.editor.font_size());
let small_text_layout = self.get_text_layout(line, font_size);
x_shift = normal_text_layout.text.hit_position(col).point.x
- small_text_layout.text.hit_position(col).point.x;
}
}
let x = self.line_point_of_line_col(line, col, font_size).x + x_shift;
(
Point::new(x, y as f64),
Point::new(x, (y + line_height) as f64),
)
}
pub fn actual_line(&self, visual_line: usize, bottom_affinity: bool) -> usize {
self.kind.with_untracked(|kind| match kind {
EditorViewKind::Normal => visual_line,
EditorViewKind::Diff(diff) => {
let is_right = diff.is_right;
let mut actual_line: usize = 0;
let mut current_visual_line = 0;
let mut last_change: Option<&DiffLines> = None;
let mut changes = diff.changes.iter().peekable();
while let Some(change) = changes.next() {
match (is_right, change) {
(true, DiffLines::Left(range)) => {
if let Some(DiffLines::Right(_)) = changes.peek() {
} else {
current_visual_line += range.len();
if current_visual_line >= visual_line {
return if bottom_affinity {
actual_line
} else {
actual_line.saturating_sub(1)
};
}
}
}
(false, DiffLines::Right(range)) => {
let len = if let Some(DiffLines::Left(r)) = last_change {
range.len() - r.len().min(range.len())
} else {
range.len()
};
if len > 0 {
current_visual_line += len;
if current_visual_line >= visual_line {
return actual_line;
}
}
}
(true, DiffLines::Right(range))
| (false, DiffLines::Left(range)) => {
let len = range.len();
if current_visual_line + len > visual_line {
return range.start
+ (visual_line - current_visual_line);
}
current_visual_line += len;
actual_line += len;
if is_right {
if let Some(DiffLines::Left(r)) = last_change {
let len = r.len() - r.len().min(range.len());
if len > 0 {
current_visual_line += len;
if current_visual_line > visual_line {
return if bottom_affinity {
actual_line
} else {
actual_line - range.len()
};
}
}
}
}
}
(_, DiffLines::Both(info)) => {
let len = info.right.len();
let start = if is_right {
info.right.start
} else {
info.left.start
};
if let Some(skip) = info.skip.as_ref() {
if current_visual_line + skip.start == visual_line {
return if bottom_affinity {
actual_line + skip.end
} else {
(actual_line + skip.start).saturating_sub(1)
};
} else if current_visual_line + skip.start + 1
> visual_line
{
return actual_line + visual_line
- current_visual_line;
} else if current_visual_line + len - skip.len() + 1
>= visual_line
{
return actual_line
+ skip.end
+ (visual_line
- current_visual_line
- skip.start
- 1);
}
actual_line += len;
current_visual_line += len - skip.len() + 1;
} else {
if current_visual_line + len > visual_line {
return start
+ (visual_line - current_visual_line);
}
current_visual_line += len;
actual_line += len;
}
}
}
last_change = Some(change);
}
actual_line
}
})
}
pub fn visual_line(&self, line: usize) -> usize {
self.kind.with_untracked(|kind| match kind {
EditorViewKind::Normal => line,
EditorViewKind::Diff(diff) => {
let is_right = diff.is_right;
let mut last_change: Option<&DiffLines> = None;
let mut visual_line = 0;
let mut changes = diff.changes.iter().peekable();
while let Some(change) = changes.next() {
match (is_right, change) {
(true, DiffLines::Left(range)) => {
if let Some(DiffLines::Right(_)) = changes.peek() {
} else {
visual_line += range.len();
}
}
(false, DiffLines::Right(range)) => {
let len = if let Some(DiffLines::Left(r)) = last_change {
range.len() - r.len().min(range.len())
} else {
range.len()
};
if len > 0 {
visual_line += len;
}
}
(true, DiffLines::Right(range))
| (false, DiffLines::Left(range)) => {
if line < range.end {
return visual_line + line - range.start;
}
visual_line += range.len();
if is_right {
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)) => {
let end = if is_right {
info.right.end
} else {
info.left.end
};
if line >= end {
visual_line += info.right.len()
- info
.skip
.as_ref()
.map(|skip| skip.len().saturating_sub(1))
.unwrap_or(0);
last_change = Some(change);
continue;
}
let start = if is_right {
info.right.start
} else {
info.left.start
};
if let Some(skip) = info.skip.as_ref() {
if start + skip.start > line {
return visual_line + line - start;
} else if start + skip.end > line {
return visual_line + skip.start;
} else {
return visual_line
+ (line - start - skip.len() + 1);
}
} else {
return visual_line + line - start;
}
}
}
last_change = Some(change);
}
visual_line
}
})
}
/// Get the offset of a particular point within the editor.
/// The boolean indicates whether the point is inside the text or not
/// Points outside of vertical bounds will return the last line.
/// Points outside of horizontal bounds will return the last column on the line.
pub fn offset_of_point(&self, mode: Mode, point: Point) -> (usize, bool) {
let ((line, col), is_inside) = self.line_col_of_point(mode, point);
(self.offset_of_line_col(line, col), is_inside)
}
/// Get the (line, col) of a particular point within the editor.
/// The boolean indicates whether the point is within the text bounds.
/// Points outside of vertical bounds will return the last line.
/// Points outside of horizontal bounds will return the last column on the line.
pub fn line_col_of_point(
&self,
mode: Mode,
point: Point,
) -> ((usize, usize), bool) {
let config = self.config.get_untracked();
let visual_line =
(point.y / config.editor.line_height() as f64).floor() as usize;
let line = self.actual_line(visual_line, true);
let line = line.min(self.last_line());
let font_size = config.editor.font_size();
let text_layout = self.get_text_layout(line, font_size);
let hit_point = text_layout.text.hit_point(Point::new(point.x, 0.0));
// We have to unapply the phantom text shifting in order to get back to the column in
// the actual buffer
let phantom_text = self.line_phantom_text(line);
let col = phantom_text.before_col(hit_point.index);
// Ensure that the column doesn't end up out of bounds, so things like clicking on the far
// right end will just go to the end of the line.
let max_col = self.line_end_col(line, mode != Mode::Normal);
let mut col = col.min(max_col);
if config.editor.atomic_soft_tabs && config.editor.tab_width > 1 {
col = snap_to_soft_tab_line_col(
&self.text(),
line,
col,
SnapDirection::Nearest,
config.editor.tab_width,
);
}
((line, col), hit_point.is_inside)
}
pub fn line_horiz_col(
&self,
line: usize,
font_size: usize,
horiz: &ColPosition,
caret: bool,
) -> usize {
match *horiz {
ColPosition::Col(x) => {
let text_layout = self.get_text_layout(line, font_size);
let hit_point = text_layout.text.hit_point(Point::new(x, 0.0));
let n = hit_point.index;
n.min(self.line_end_col(line, caret))
}
ColPosition::End => self.line_end_col(line, caret),
ColPosition::Start => 0,
ColPosition::FirstNonBlank => {
self.first_non_blank_character_on_line(line)
}
}
}
/// Advance to the right in the manner of the given mode.
/// This is not the same as the [`Movement::Right`] command.
pub fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().move_right(offset, mode, count))
}
/// Advance to the left in the manner of the given mode.
/// This is not the same as the [`Movement::Left`] command.
pub fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
self.doc
.with_untracked(|doc| doc.buffer().move_left(offset, mode, count))
}
/// Find the next/previous offset of the match of the given character.
/// This is intended for use by the [`Movement::NextUnmatched`] and
/// [`Movement::PreviousUnmatched`] commands.
pub fn find_unmatched(&self, offset: usize, previous: bool, ch: char) -> usize {
// This needs the doc's syntax, but it isn't cheap to clone
// so this has to be a method on view for now.
self.doc.with_untracked(|doc| {
let syntax = doc.syntax();
if syntax.layers.is_some() {
syntax
.find_tag(offset, previous, &CharBuffer::from(ch))
.unwrap_or(offset)
} else {
let text = self.text();
let mut cursor = WordCursor::new(&text, offset);
let new_offset = if previous {
cursor.previous_unmatched(ch)
} else {
cursor.next_unmatched(ch)
};
new_offset.unwrap_or(offset)
}
})
}
/// Find the offset of the matching pair character.
/// This is intended for use by the [`Movement::MatchPairs`] command.
pub fn find_matching_pair(&self, offset: usize) -> usize {
// This needs the doc's syntax, but it isn't cheap to clone
// so this has to be a method on view for now.
self.doc.with_untracked(|doc| {
let syntax = doc.syntax();
if syntax.layers.is_some() {
syntax.find_matching_pair(offset).unwrap_or(offset)
} else {
WordCursor::new(&self.text(), offset)
.match_pairs()
.unwrap_or(offset)
}
})
}
}
+264 -66
View File
@@ -1,20 +1,34 @@
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use floem::{
peniko::kurbo::{Point, Rect},
reactive::{RwSignal, Scope},
peniko::{
kurbo::{Point, Rect},
Color,
},
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::{
doc::{DocContent, Document},
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
doc::{Doc, DocContent},
editor::{
diff::{DiffEditorData, DiffEditorInfo},
location::EditorLocation,
EditorData, EditorInfo,
},
id::{DiffEditorId, EditorId, EditorTabId, KeymapId, SettingsId, SplitId},
main_split::MainSplitData,
id::{
DiffEditorId, EditorTabId, KeymapId, SettingsId, SplitId,
ThemeColorSettingsId, VoltViewId,
},
main_split::{Editors, MainSplitData},
plugin::PluginData,
window_tab::WindowTabData,
};
@@ -23,7 +37,9 @@ pub enum EditorTabChildInfo {
Editor(EditorInfo),
DiffEditor(DiffEditorInfo),
Settings,
ThemeColorSettings,
Keymap,
Volt(VoltID),
}
impl EditorTabChildInfo {
@@ -34,10 +50,8 @@ impl EditorTabChildInfo {
) -> EditorTabChild {
match &self {
EditorTabChildInfo::Editor(editor_info) => {
let editor_data = editor_info.to_data(data, editor_tab_id);
EditorTabChild::Editor(
editor_data.with_untracked(|editor_data| editor_data.editor_id),
)
let editor_id = editor_info.to_data(data, editor_tab_id);
EditorTabChild::Editor(editor_id)
}
EditorTabChildInfo::DiffEditor(diff_editor_info) => {
let diff_editor_data = diff_editor_info.to_data(data, editor_tab_id);
@@ -46,7 +60,13 @@ impl EditorTabChildInfo {
EditorTabChildInfo::Settings => {
EditorTabChild::Settings(SettingsId::next())
}
EditorTabChildInfo::ThemeColorSettings => {
EditorTabChild::ThemeColorSettings(ThemeColorSettingsId::next())
}
EditorTabChildInfo::Keymap => EditorTabChild::Keymap(KeymapId::next()),
EditorTabChildInfo::Volt(id) => {
EditorTabChild::Volt(VoltViewId::next(), id.to_owned())
}
}
}
}
@@ -78,6 +98,7 @@ impl EditorTabInfo {
.map(|child| {
(
cx.create_rw_signal(0),
cx.create_rw_signal(Rect::ZERO),
child.to_data(data.clone(), editor_tab_id),
)
})
@@ -100,17 +121,13 @@ impl EditorTabInfo {
}
pub enum EditorTabChildSource {
Editor {
path: PathBuf,
doc: RwSignal<Document>,
},
DiffEditor {
left: RwSignal<Document>,
right: RwSignal<Document>,
},
Editor { path: PathBuf, doc: Rc<Doc> },
DiffEditor { left: Rc<Doc>, right: Rc<Doc> },
NewFileEditor,
Settings,
ThemeColorSettings,
Keymap,
Volt(VoltID),
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -118,7 +135,18 @@ pub enum EditorTabChild {
Editor(EditorId),
DiffEditor(DiffEditorId),
Settings(SettingsId),
ThemeColorSettings(ThemeColorSettingsId),
Keymap(KeymapId),
Volt(VoltViewId, VoltID),
}
#[derive(PartialEq)]
pub struct EditorTabChildViewInfo {
pub icon: String,
pub color: Option<Color>,
pub path: String,
pub confirmed: Option<RwSignal<bool>>,
pub is_pristine: bool,
}
impl EditorTabChild {
@@ -127,7 +155,9 @@ impl EditorTabChild {
EditorTabChild::Editor(id) => id.to_raw(),
EditorTabChild::DiffEditor(id) => id.to_raw(),
EditorTabChild::Settings(id) => id.to_raw(),
EditorTabChild::ThemeColorSettings(id) => id.to_raw(),
EditorTabChild::Keymap(id) => id.to_raw(),
EditorTabChild::Volt(id, _) => id.to_raw(),
}
}
@@ -141,13 +171,9 @@ impl EditorTabChild {
let editor_data = data
.main_split
.editors
.get_untracked()
.get(editor_id)
.cloned()
.editor_untracked(*editor_id)
.unwrap();
EditorTabChildInfo::Editor(
editor_data.get_untracked().editor_info(data),
)
EditorTabChildInfo::Editor(editor_data.editor_info(data))
}
EditorTabChild::DiffEditor(diff_editor_id) => {
let diff_editor_data = data
@@ -160,7 +186,206 @@ impl EditorTabChild {
EditorTabChildInfo::DiffEditor(diff_editor_data.diff_editor_info())
}
EditorTabChild::Settings(_) => EditorTabChildInfo::Settings,
EditorTabChild::ThemeColorSettings(_) => {
EditorTabChildInfo::ThemeColorSettings
}
EditorTabChild::Keymap(_) => EditorTabChildInfo::Keymap,
EditorTabChild::Volt(_, id) => EditorTabChildInfo::Volt(id.to_owned()),
}
}
pub fn view_info(
&self,
editors: Editors,
diff_editors: RwSignal<im::HashMap<DiffEditorId, DiffEditorData>>,
plugin: PluginData,
config: ReadSignal<Arc<LapceConfig>>,
) -> Memo<EditorTabChildViewInfo> {
match self.clone() {
EditorTabChild::Editor(editor_id) => create_memo(move |_| {
let config = config.get();
let editor_data = editors.editor(editor_id);
let path = if let Some(editor_data) = editor_data {
let doc = editor_data.doc_signal().get();
let (content, is_pristine, confirmed) = (
doc.content.get(),
doc.buffer.with(|b| b.is_pristine()),
editor_data.confirmed,
);
match content {
DocContent::File { path, .. } => {
Some((path, confirmed, is_pristine))
}
DocContent::Local => None,
DocContent::History(_) => None,
DocContent::Scratch { name, .. } => {
Some((PathBuf::from(name), confirmed, is_pristine))
}
}
} else {
None
};
let (icon, color, path, confirmed, is_pristine) = match path {
Some((path, confirmed, is_pritine)) => {
let (svg, color) = config.file_svg(&path);
(
svg,
color,
path.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
confirmed,
is_pritine,
)
}
None => (
config.ui_svg(LapceIcons::FILE),
Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
"local".to_string(),
create_rw_signal(true),
true,
),
};
EditorTabChildViewInfo {
icon,
color,
path,
confirmed: Some(confirmed),
is_pristine,
}
}),
EditorTabChild::DiffEditor(diff_editor_id) => create_memo(move |_| {
let config = config.get();
let diff_editor_data = diff_editors
.with(|diff_editors| diff_editors.get(&diff_editor_id).cloned());
let confirmed = diff_editor_data.as_ref().map(|d| d.confirmed);
let info = diff_editor_data
.map(|diff_editor_data| {
[diff_editor_data.left, diff_editor_data.right].map(|data| {
let (content, is_pristine) =
data.doc_signal().with(|doc| {
(
doc.content.get(),
doc.buffer.with(|b| b.is_pristine()),
)
});
match content {
DocContent::File { path, .. } => {
Some((path, is_pristine))
}
DocContent::Local => None,
DocContent::History(_) => None,
DocContent::Scratch { name, .. } => {
Some((PathBuf::from(name), is_pristine))
}
}
})
})
.unwrap_or([None, None]);
let (icon, color, path, is_pristine) = match info {
[Some((path, is_pristine)), None]
| [None, Some((path, is_pristine))] => {
let (svg, color) = config.file_svg(&path);
(
svg,
color,
format!(
"{} (Diff)",
path.file_name()
.unwrap_or_default()
.to_string_lossy()
),
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] =
[&left_path, &right_path].map(|path| {
path.file_name()
.unwrap_or_default()
.to_string_lossy()
});
(
svg,
color,
format!("{left_file_name} - {right_file_name} (Diff)"),
left_is_pristine && right_is_pristine,
)
}
[None, None] => (
config.ui_svg(LapceIcons::FILE),
Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
"local".to_string(),
true,
),
};
EditorTabChildViewInfo {
icon,
color,
path,
confirmed,
is_pristine,
}
}),
EditorTabChild::Settings(_) => create_memo(move |_| {
let config = config.get();
EditorTabChildViewInfo {
icon: config.ui_svg(LapceIcons::SETTINGS),
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
path: "Settings".to_string(),
confirmed: None,
is_pristine: true,
}
}),
EditorTabChild::ThemeColorSettings(_) => create_memo(move |_| {
let config = config.get();
EditorTabChildViewInfo {
icon: config.ui_svg(LapceIcons::SYMBOL_COLOR),
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
path: "Theme Colors".to_string(),
confirmed: None,
is_pristine: true,
}
}),
EditorTabChild::Keymap(_) => create_memo(move |_| {
let config = config.get();
EditorTabChildViewInfo {
icon: config.ui_svg(LapceIcons::KEYBOARD),
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
path: "Keyboard Shortcuts".to_string(),
confirmed: None,
is_pristine: true,
}
}),
EditorTabChild::Volt(_, id) => create_memo(move |_| {
let config = config.get();
let display_name = plugin
.installed
.with(|volts| volts.get(&id).cloned())
.map(|volt| volt.meta.with(|m| m.display_name.clone()))
.or_else(|| {
plugin.available.volts.with(|volts| {
let volt = volts.get(&id);
volt.map(|volt| {
volt.info.with(|m| m.display_name.clone())
})
})
})
.unwrap_or_else(|| id.name.clone());
EditorTabChildViewInfo {
icon: config.ui_svg(LapceIcons::EXTENSIONS),
color: Some(config.color(LapceColor::LAPCE_ICON_ACTIVE)),
path: display_name,
confirmed: None,
is_pristine: true,
}
}),
}
}
}
@@ -171,7 +396,7 @@ pub struct EditorTabData {
pub split: SplitId,
pub editor_tab_id: EditorTabId,
pub active: usize,
pub children: Vec<(RwSignal<usize>, EditorTabChild)>,
pub children: Vec<(RwSignal<usize>, RwSignal<Rect>, EditorTabChild)>,
pub window_origin: Point,
pub layout_rect: Rect,
pub locations: RwSignal<im::Vector<EditorLocation>>,
@@ -181,22 +406,21 @@ pub struct EditorTabData {
impl EditorTabData {
pub fn get_editor(
&self,
editors: &im::HashMap<EditorId, RwSignal<EditorData>>,
editors: Editors,
path: &Path,
) -> Option<(usize, RwSignal<EditorData>)> {
) -> Option<(usize, EditorData)> {
for (i, child) in self.children.iter().enumerate() {
if let (_, EditorTabChild::Editor(editor_id)) = child {
if let Some(editor) = editors.get(editor_id) {
let e = editor.get_untracked();
let is_path = e.view.doc.with_untracked(|doc| {
if let DocContent::File(p) = &doc.content {
if let (_, _, EditorTabChild::Editor(editor_id)) = child {
if let Some(editor) = editors.editor_untracked(*editor_id) {
let is_path = editor.doc().content.with_untracked(|content| {
if let DocContent::File { path: p, .. } = content {
p == path
} else {
false
}
});
if is_path {
return Some((i, *editor));
return Some((i, editor));
}
}
}
@@ -206,15 +430,14 @@ impl EditorTabData {
pub fn get_unconfirmed_editor_tab_child(
&self,
editors: &im::HashMap<EditorId, RwSignal<EditorData>>,
editors: Editors,
diff_editors: &im::HashMap<EditorId, DiffEditorData>,
) -> Option<(usize, EditorTabChild)> {
for (i, (_, child)) in self.children.iter().enumerate() {
for (i, (_, _, child)) in self.children.iter().enumerate() {
match child {
EditorTabChild::Editor(editor_id) => {
if let Some(editor) = editors.get(editor_id) {
let e = editor.get_untracked();
let confirmed = e.confirmed.get_untracked();
if let Some(editor) = editors.editor_untracked(*editor_id) {
let confirmed = editor.confirmed.get_untracked();
if !confirmed {
return Some((i, child.clone()));
}
@@ -222,15 +445,8 @@ impl EditorTabData {
}
EditorTabChild::DiffEditor(diff_editor_id) => {
if let Some(diff_editor) = diff_editors.get(diff_editor_id) {
let left_confirmed =
diff_editor.left.with_untracked(|editor| {
editor.confirmed.get_untracked()
});
let right_confirmed =
diff_editor.right.with_untracked(|editor| {
editor.confirmed.get_untracked()
});
if !left_confirmed && !right_confirmed {
let confirmed = diff_editor.confirmed.get_untracked();
if !confirmed {
return Some((i, child.clone()));
}
}
@@ -241,24 +457,6 @@ impl EditorTabData {
None
}
pub fn get_unconfirmed_editor(
&self,
editors: &im::HashMap<EditorId, RwSignal<EditorData>>,
) -> Option<(usize, RwSignal<EditorData>)> {
for (i, child) in self.children.iter().enumerate() {
if let (_, EditorTabChild::Editor(editor_id)) = child {
if let Some(editor) = editors.get(editor_id) {
let e = editor.get_untracked();
let confirmed = e.confirmed.get_untracked();
if !confirmed {
return Some((i, *editor));
}
}
}
}
None
}
pub fn tab_info(&self, data: &WindowTabData) -> EditorTabInfo {
let info = EditorTabInfo {
active: self.active,
@@ -267,7 +465,7 @@ impl EditorTabData {
children: self
.children
.iter()
.map(|(_, child)| child.child_info(data))
.map(|(_, _, child)| child.child_info(data))
.collect(),
};
info
+594 -33
View File
@@ -1,44 +1,605 @@
use std::path::PathBuf;
use std::{
borrow::Cow,
collections::HashMap,
ffi::OsStr,
path::{Path, PathBuf},
rc::Rc,
};
use floem::reactive::{RwSignal, Scope};
use indexmap::IndexMap;
use floem::{
action::show_context_menu,
event::EventPropagation,
ext_event::create_ext_action,
keyboard::Modifiers,
menu::{Menu, MenuItem},
reactive::{RwSignal, Scope},
views::editor::text::SystemClipboard,
};
use globset::Glob;
use lapce_core::{
command::{EditCommand, FocusCommand},
mode::Mode,
register::Clipboard,
};
use lapce_rpc::{
file::{Duplicating, FileNodeItem, Naming, NamingState, NewNode, Renaming},
proxy::ProxyResponse,
};
use tracing::{event, Level};
use url::Url;
use super::node::FileNode;
use crate::window_tab::CommonData;
use crate::{
command::{CommandExecuted, CommandKind, InternalCommand, LapceCommand},
editor::EditorData,
keypress::{condition::Condition, KeyPressFocus},
main_split::Editors,
window_tab::CommonData,
};
enum RenamedPath {
NotRenaming,
NameUnchanged,
Renamed { current_path: Url, new_path: Url },
}
#[derive(Clone)]
pub struct FileExplorerData {
pub root: FileNode,
pub common: CommonData,
pub all_files: RwSignal<im::HashMap<PathBuf, FileNode>>,
pub root: RwSignal<FileNodeItem>,
pub naming: RwSignal<Naming>,
pub naming_editor_data: EditorData,
pub common: Rc<CommonData>,
left_diff_path: RwSignal<Option<PathBuf>>,
}
impl FileExplorerData {
pub fn new(cx: Scope, common: CommonData) -> Self {
let path = common.workspace.path.clone().unwrap_or_default();
let all_files = cx.create_rw_signal(im::HashMap::new());
let root = FileNode {
scope: cx,
path: path.clone(),
is_dir: true,
read: cx.create_rw_signal(false),
expanded: cx.create_rw_signal(false),
children: cx.create_rw_signal(IndexMap::new()),
children_open_count: cx.create_rw_signal(0),
all_files,
line_height: common.ui_line_height,
internal_command: common.internal_command,
};
all_files.update(|all_files| {
all_files.insert(path, root.clone());
});
if common.workspace.path.is_some() {
root.toggle_expand(&common.proxy);
impl KeyPressFocus for FileExplorerData {
fn get_mode(&self) -> Mode {
Mode::Insert
}
fn check_condition(&self, condition: Condition) -> bool {
self.naming.with_untracked(Naming::is_accepting_input)
&& condition == Condition::ModalFocus
}
fn run_command(
&self,
command: &LapceCommand,
count: Option<usize>,
mods: Modifiers,
) -> CommandExecuted {
if self.naming.with_untracked(Naming::is_accepting_input) {
match command.kind {
CommandKind::Focus(FocusCommand::ModalClose) => {
self.cancel_naming();
CommandExecuted::Yes
}
CommandKind::Edit(EditCommand::InsertNewLine) => {
self.finish_naming();
CommandExecuted::Yes
}
CommandKind::Edit(_) => {
let command_executed =
self.naming_editor_data.run_command(command, count, mods);
if let Some(new_path) = self.naming_path() {
self.common
.internal_command
.send(InternalCommand::TestPathCreation { new_path });
}
command_executed
}
_ => self.naming_editor_data.run_command(command, count, mods),
}
} else {
CommandExecuted::No
}
Self {
root,
common,
all_files,
}
fn receive_char(&self, c: &str) {
if self.naming.with_untracked(Naming::is_accepting_input) {
self.naming_editor_data.receive_char(c);
if let Some(new_path) = self.naming_path() {
self.common
.internal_command
.send(InternalCommand::TestPathCreation { new_path });
}
}
}
}
impl FileExplorerData {
pub fn new(cx: Scope, editors: Editors, common: Rc<CommonData>) -> Self {
let path = common
.workspace
.path
.clone()
.and_then(|w| Url::from_directory_path(w).ok());
let root = cx.create_rw_signal(FileNodeItem {
path: path.unwrap_or_default(),
is_dir: true,
read: false,
open: false,
children: HashMap::new(),
children_open_count: 0,
});
let naming = cx.create_rw_signal(Naming::None);
let naming_editor_data = editors.make_local(cx, common.clone());
let data = Self {
root,
naming,
naming_editor_data,
common,
left_diff_path: cx.create_rw_signal(None),
};
if data.common.workspace.path.is_some() {
// only fill in the child files if there is open folder
data.toggle_expand(&mut &path.unwrap());
}
data
}
/// Reload the file explorer data via reading the root directory.
/// Note that this will not update immediately.
pub fn reload(&self) {
let path = self.root.with_untracked(|root| root.path.clone());
self.read_dir(&path);
}
/// 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: &mut Url) {
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);
}
read
})
.unwrap()
else {
return;
};
// Read the directory's files if they haven't been read yet
if !read {
self.read_dir(path);
}
}
pub fn read_dir(&self, path: &Url) {
self.read_dir_cb(path, |_| {});
}
/// Read the directory's information and update the file explorer tree.
/// `done : FnOnce(was_read: bool)` is called when the operation is completed, whether success,
/// failure, or ignored.
pub fn read_dir_cb(&self, path: &Url, done: impl FnOnce(bool) + 'static) {
let root = self.root;
let data = self.clone();
let config = self.common.config;
let send = {
create_ext_action(self.common.scope, move |result| {
let Ok(ProxyResponse::ReadDirResponse { mut items }) = result else {
done(false);
return;
};
root.update(|root| {
// Get the node for this path, which should already exist if we're calling
// read_dir on it.
if let Some(node) = root.get_file_node_mut(&mut &path) {
// TODO: do not recreate glob every time we read a directory
// Retain only items that are not excluded from view by the configuration
match Glob::new(&config.get().editor.files_exclude) {
Ok(glob) => {
let matcher = glob.compile_matcher();
items.retain(|i| {
!matcher
.is_match(&i.path.to_file_path().unwrap())
});
}
Err(e) => tracing::error!(
target:"files_exclude",
"Failed to compile glob: {}",
e
),
}
node.read = true;
// Remove paths that no longer exist
let removed_paths = node
.children
.keys()
.filter(|p| !items.iter().any(|i| &&i.path == p))
.collect();
for path in removed_paths {
node.children.remove(&path);
}
// Reread dirs that were already read and add new paths
for item in items {
if let Some(existing) = node.children.get(&item.path) {
if existing.read {
data.read_dir(&existing.path);
}
} else {
node.children.insert(item.path.clone(), item);
}
}
}
root.update_node_count_recursive(&mut &path);
});
done(true);
})
};
// Ask the proxy for the directory information
self.common.proxy.read_dir(path.clone(), send);
}
/// Returns `true` if `path` exists in the file explorer tree and is a directory, `false`
/// otherwise.
fn is_dir(&self, path: &Path) -> bool {
self.root.with_untracked(|root| {
root.get_file_node(&mut Url::from_directory_path(path.clone()).unwrap())
.is_some_and(|node| node.is_dir)
})
}
/// The current path that we're renaming to / creating or duplicating a node at.
/// Note: returns `None` when renaming if the file name has not changed.
fn naming_path(&self) -> Option<Url> {
self.naming.with_untracked(|naming| match naming {
Naming::None => None,
Naming::Renaming(_) => {
if let RenamedPath::Renamed { new_path, .. } = self.renamed_path() {
Some(new_path)
} else {
None
}
}
Naming::NewNode(n) => {
let relative_path = self.naming_editor_data.text();
Some(Url::from_file_path(relative_path).ok()?)
}
Naming::Duplicating(d) => {
let relative_path = self.naming_editor_data.text();
let new_path = lapce_rpc::file::parent(&d.path)
.and_then(|p| p.join(relative_path.to_owned()).ok())
.unwrap();
Some(new_path)
}
})
}
/// If there is an in progress rename and the user has entered a path that differs from the
/// current path, gets the current and new paths of the renamed node.
fn renamed_path(&self) -> RenamedPath {
self.naming.with_untracked(|naming| {
if let Some(current_path) = naming.as_renaming().map(|x| &x.path) {
let current_file_name = current_path.file_name().unwrap_or_default();
// `new_relative_path` is the new path relative to the parent directory, unless the
// user has entered an absolute path.
let new_relative_path = self.naming_editor_data.text();
let new_relative_path: Cow<OsStr> =
match new_relative_path.slice_to_cow(..) {
Cow::Borrowed(path) => Cow::Borrowed(path.as_ref()),
Cow::Owned(path) => Cow::Owned(path.into()),
};
if new_relative_path == current_file_name {
RenamedPath::NameUnchanged
} else {
let new_path = lapce_rpc::file::parent(current_path)
.and_then(|p| p.join(new_relative_path).ok())
.unwrap();
RenamedPath::Renamed {
current_path: current_path.to_owned(),
new_path,
}
}
} else {
RenamedPath::NotRenaming
}
})
}
/// If a naming is in progress and the user has entered a valid path, send the request to
/// actually perform the change.
pub fn finish_naming(&self) {
match self.naming.get_untracked() {
Naming::None => {}
Naming::Renaming(_) => {
let renamed_path = self.renamed_path();
match renamed_path {
// Should not occur
RenamedPath::NotRenaming => {}
RenamedPath::NameUnchanged => {
self.cancel_naming();
}
RenamedPath::Renamed {
current_path,
new_path,
} => {
self.common.internal_command.send(
InternalCommand::FinishRenamePath {
current_path,
new_path,
},
);
}
}
}
Naming::NewNode(n) => {
let Some(path) = self.naming_path() else {
return;
};
self.common
.internal_command
.send(InternalCommand::FinishNewNode {
is_dir: n.is_dir,
path,
});
}
Naming::Duplicating(d) => {
let Some(path) = self.naming_path() else {
return;
};
self.common.internal_command.send(
InternalCommand::FinishDuplicate {
source: d.path.to_owned(),
path,
},
);
}
}
}
/// Closes the naming text box without applying the effect.
pub fn cancel_naming(&self) {
self.naming.set(Naming::None);
}
pub fn click(&self, path: &Path) {
if self.is_dir(path) {
self.toggle_expand(path);
} else {
self.common
.internal_command
.send(InternalCommand::OpenFile {
path: path.to_path_buf(),
})
}
}
pub fn double_click(&self, path: &Path) -> EventPropagation {
if self.is_dir(path) {
EventPropagation::Continue
} else {
self.common
.internal_command
.send(InternalCommand::MakeConfirmed);
EventPropagation::Stop
}
}
pub fn secondary_click(&self, path: &Path) {
let common = self.common.clone();
let path_a = path.to_owned();
let left_diff_path = self.left_diff_path;
// TODO: should we just pass is_dir into secondary click?
let is_dir = self.is_dir(path);
let Some(workspace_path) = self.common.workspace.path.as_ref() else {
// There is no context menu if we are not in a workspace
return;
};
let is_workspace = path == workspace_path;
let base_path_a = if is_dir {
Some(path_a.clone())
} else {
path_a.parent().map(ToOwned::to_owned)
};
let base_path_a = base_path_a.as_ref().unwrap_or(workspace_path);
let mut menu = Menu::new("");
let base_path = base_path_a.clone();
let data = self.clone();
let naming = self.naming;
menu = menu.entry(MenuItem::new("New File").action(move || {
let base_path_b = &base_path;
let base_path = base_path.clone();
data.read_dir_cb(base_path_b, move |was_read| {
if !was_read {
tracing::warn!(
"Failed to read directory, avoiding creating node in: {:?}",
base_path
);
return;
}
naming.set(Naming::NewNode(NewNode {
state: NamingState::Naming,
base_path: base_path.clone(),
is_dir: false,
editor_needs_reset: true,
}));
});
}));
let base_path = base_path_a.clone();
let data = self.clone();
let naming = self.naming;
menu = menu.entry(MenuItem::new("New Directory").action(move || {
let base_path_b = &base_path;
let base_path = base_path.clone();
data.read_dir_cb(base_path_b, move |was_read| {
if !was_read {
tracing::warn!(
"Failed to read directory, avoiding creating node in: {:?}",
base_path
);
return;
}
naming.set(Naming::NewNode(NewNode {
state: NamingState::Naming,
base_path: base_path.clone(),
is_dir: true,
editor_needs_reset: true,
}));
})
}));
menu = menu.separator();
// 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;
}
if let Err(err) = open::that(path) {
tracing::error!(
"Failed to reveal file in system file explorer: {}",
err
);
}
},
));
}
if !is_workspace {
let path = path_a.clone();
menu = menu.entry(MenuItem::new("Rename").action(move || {
naming.set(Naming::Renaming(Renaming {
state: NamingState::Naming,
path: path.clone(),
editor_needs_reset: true,
}));
}));
let path = path_a.clone();
menu = menu.entry(MenuItem::new("Duplicate").action(move || {
naming.set(Naming::Duplicating(Duplicating {
state: NamingState::Naming,
path: path.clone(),
editor_needs_reset: true,
}));
}));
// TODO: it is common for shift+right click to make 'Move file to trash' an actual
// Delete, which can be useful for large files.
let path = path_a.clone();
let proxy = common.proxy.clone();
let trash_text = if is_dir {
"Move Directory to Trash"
} else {
"Move File to Trash"
};
menu = menu.entry(MenuItem::new(trash_text).action(move || {
proxy.trash_path(path.clone(), |res| {
if let Err(err) = res {
tracing::warn!("Failed to trash path: {:?}", err);
}
})
}));
}
menu = menu.separator();
let path = path_a.clone();
menu = menu.entry(MenuItem::new("Copy Path").action(move || {
let mut clipboard = SystemClipboard::new();
clipboard.put_string(path.to_string_lossy());
}));
let path = path_a.clone();
let workspace = common.workspace.clone();
menu = menu.entry(MenuItem::new("Copy Relative Path").action(move || {
let relative_path = if let Some(workspace_path) = &workspace.path {
path.strip_prefix(workspace_path).unwrap_or(&path)
} else {
path.as_ref()
};
let mut clipboard = SystemClipboard::new();
clipboard.put_string(relative_path.to_string_lossy());
}));
menu = menu.separator();
let path = path_a.clone();
menu = menu.entry(
MenuItem::new("Select for Compare")
.action(move || left_diff_path.set(Some(path.clone()))),
);
if let Some(left_path) = self.left_diff_path.get_untracked() {
let common = self.common.clone();
let right_path = path_a.to_owned();
menu = menu.entry(MenuItem::new("Compare with Selected").action(
move || {
common
.internal_command
.send(InternalCommand::OpenDiffFiles {
left_path: left_path.clone(),
right_path: right_path.clone(),
})
},
))
}
menu = menu.separator();
let internal_command = common.internal_command;
menu = menu.entry(MenuItem::new("Refresh").action(move || {
internal_command.send(InternalCommand::ReloadFileExplorer);
}));
show_context_menu(menu, None);
}
pub fn middle_click(&self, path: &Path) -> EventPropagation {
if self.is_dir(path) {
EventPropagation::Continue
} else {
self.common
.internal_command
.send(InternalCommand::OpenFileInNewTab {
path: path.to_path_buf(),
});
EventPropagation::Stop
}
}
}
+28 -143
View File
@@ -1,153 +1,38 @@
use std::path::PathBuf;
use floem::views::VirtualVector;
use lapce_rpc::file::{FileNodeItem, FileNodeViewData, Naming};
use floem::{
ext_event::create_ext_action,
reactive::{Memo, RwSignal, Scope},
views::VirtualListVector,
};
use indexmap::IndexMap;
use lapce_rpc::proxy::{ProxyResponse, ProxyRpcHandler};
use crate::{command::InternalCommand, listener::Listener};
#[derive(Clone)]
pub struct FileNode {
pub scope: Scope,
pub path: PathBuf,
pub is_dir: bool,
pub read: RwSignal<bool>,
pub expanded: RwSignal<bool>,
pub children: RwSignal<IndexMap<PathBuf, FileNode>>,
pub children_open_count: RwSignal<usize>,
pub all_files: RwSignal<im::HashMap<PathBuf, FileNode>>,
pub line_height: Memo<f64>,
pub internal_command: Listener<InternalCommand>,
pub struct FileNodeVirtualList {
file_node_item: FileNodeItem,
naming: Naming,
}
impl VirtualListVector<(PathBuf, FileNode)> for FileNode {
type ItemIterator = Box<dyn Iterator<Item = (PathBuf, FileNode)>>;
fn total_size(&self) -> Option<f64> {
let line_height = self.line_height.get();
let count = self.children_open_count.get() + 1;
Some(line_height * count as f64)
impl FileNodeVirtualList {
pub fn new(file_node_item: FileNodeItem, naming: Naming) -> Self {
Self {
file_node_item,
naming,
}
}
}
impl VirtualVector<FileNodeViewData> for FileNodeVirtualList {
fn total_len(&self) -> usize {
0
self.file_node_item.children_open_count
}
fn slice(&mut self, _range: std::ops::Range<usize>) -> Self::ItemIterator {
let children = if !self.is_dir {
IndexMap::new()
} else {
let expanded = self.expanded.get();
if expanded {
self.children.get()
} else {
IndexMap::new()
}
};
Box::new(children.into_iter())
}
}
impl FileNode {
pub fn click(&self, proxy: &ProxyRpcHandler) {
if self.is_dir {
self.toggle_expand(proxy)
} else {
self.internal_command.send(InternalCommand::OpenFile {
path: self.path.clone(),
})
}
}
pub fn toggle_expand(&self, proxy: &ProxyRpcHandler) {
if !self.is_dir {
return;
}
let expanded = self.expanded.get_untracked();
if expanded {
self.expanded.set(false);
self.update_open_count();
} else {
self.expanded.set(true);
if self.read.get_untracked() {
self.update_open_count();
} else {
self.read_dir(proxy);
}
}
}
fn update_open_count(&self) {
self.all_files.with_untracked(|all_files| {
for current_path in self.path.ancestors() {
if let Some(node) = all_files.get(current_path) {
node.update_node_open_count();
}
}
});
}
fn update_node_open_count(&self) {
if self.is_dir {
let expanded = self.expanded.get_untracked();
if expanded {
let count = self.children.with_untracked(|children| {
children
.values()
.map(|node| node.children_open_count.get_untracked() + 1)
.sum::<usize>()
});
self.children_open_count.set(count);
} else {
self.children_open_count.set(0);
}
}
}
fn read_dir(&self, proxy: &ProxyRpcHandler) {
if self.read.get_untracked() {
return;
}
self.read.set(true);
let cx = self.scope;
let file_node = self.clone();
let send = create_ext_action(self.scope, move |result| {
if let Ok(ProxyResponse::ReadDirResponse { items }) = result {
let items = items
.into_iter()
.map(|item| {
(
item.path_buf.clone(),
FileNode {
scope: cx,
path: item.path_buf,
is_dir: item.is_dir,
read: cx.create_rw_signal(false),
expanded: cx.create_rw_signal(false),
children: cx.create_rw_signal(IndexMap::new()),
children_open_count: cx.create_rw_signal(0),
all_files: file_node.all_files,
line_height: file_node.line_height,
internal_command: file_node.internal_command,
},
)
})
.collect::<IndexMap<PathBuf, FileNode>>();
file_node.all_files.update(|all_files| {
for (_, item) in items.iter() {
all_files.insert(item.path.clone(), item.clone());
}
});
file_node.children.set(items);
file_node.update_open_count();
}
});
proxy.read_dir(self.path.clone(), move |result| {
send(result);
})
fn slice(
&mut self,
range: std::ops::Range<usize>,
) -> impl Iterator<Item = FileNodeViewData> {
let naming = &self.naming;
let root = &self.file_node_item;
let min = range.start;
let max = range.end;
let mut view_items = Vec::new();
root.append_children_view_slice(&mut view_items, naming, min, max, 0, 0);
view_items.into_iter()
}
}
+527 -171
View File
@@ -1,198 +1,554 @@
use std::{path::PathBuf, sync::Arc};
use std::{path::Path, rc::Rc, sync::Arc};
use floem::{
cosmic_text::Style as FontStyle,
event::{Event, EventListener},
peniko::Color,
reactive::ReadSignal,
style::{CursorStyle, Style},
view::View,
reactive::{create_rw_signal, ReadSignal, RwSignal},
style::{AlignItems, CursorStyle, Position, Style},
views::{
container, container_box, label, scroll, stack, svg, virtual_list,
Decorators, VirtualListDirection, VirtualListItemSize, VirtualListVector,
container, dyn_stack, label, scroll, stack, svg, virtual_stack, Container,
Decorators, VirtualDirection, VirtualItemSize,
},
View,
};
use lapce_rpc::proxy::ProxyRpcHandler;
use lapce_core::selection::Selection;
use lapce_rpc::{
file::{FileNodeViewData, FileNodeViewKind, Naming},
source_control::FileDiffKind,
};
use lapce_xi_rope::Rope;
use super::node::FileNode;
use super::{data::FileExplorerData, node::FileNodeVirtualList};
use crate::{
app::clickable_icon,
command::InternalCommand,
config::{color::LapceColor, icon::LapceIcons, LapceConfig},
panel::{position::PanelPosition, view::panel_header},
window_tab::WindowTabData,
editor_tab::{EditorTabChild, EditorTabData},
panel::{
data::PanelSection, kind::PanelKind, position::PanelPosition,
view::PanelBuilder,
},
plugin::PluginData,
source_control::SourceControlData,
text_input::TextInputBuilder,
window_tab::{Focus, WindowTabData},
};
/// Blends `foreground` with `background`.
///
/// Uses the alpha channel from `foreground` - if `foreground` is opaque, `foreground` will be
/// returned unchanged.
///
/// 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 [r, g, 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 }
}
pub fn file_explorer_panel(
window_tab_data: Arc<WindowTabData>,
window_tab_data: Rc<WindowTabData>,
position: PanelPosition,
) -> impl View {
let config = window_tab_data.common.config;
let root_file_node = window_tab_data.file_explorer.root.clone();
let proxy = window_tab_data.common.proxy.clone();
stack(|| {
(
stack(move || (panel_header("Open Editors".to_string(), config),))
.style(|| Style::BASE.width_pct(100.0).flex_col().height_px(150.0)),
stack(|| {
(
panel_header("File Explorer".to_string(), config),
container(|| {
scroll(move || {
file_node_view(
root_file_node.clone(),
proxy.clone(),
0,
config,
)
})
.scroll_bar_color(move || {
*config.get().get_color(LapceColor::LAPCE_SCROLL_BAR)
})
.style(|| Style::BASE.absolute().size_pct(100.0, 100.0))
})
.style(|| Style::BASE.size_pct(100.0, 100.0).line_height(1.6)),
)
})
.style(|| Style::BASE.width_pct(100.0).height_pct(100.0).flex_col()),
let data = window_tab_data.file_explorer.clone();
let source_control = window_tab_data.source_control.clone();
PanelBuilder::new(config, position)
.add_height_style(
"Open Editors",
150.0,
container(open_editors_view(window_tab_data.clone()))
.style(|s| s.size_full()),
window_tab_data.panel.section_open(PanelSection::OpenEditor),
move |s| s.apply_if(!config.get().ui.open_editors_visible, Style::hide),
)
})
.style(move || {
Style::BASE
.width_pct(100.0)
.apply_if(!position.is_bottom(), |s| s.flex_col())
})
.add(
"File Explorer",
container(
new_file_node_view(data, source_control).style(|s| s.absolute()),
)
.style(|s| s.size_full().line_height(1.6)),
window_tab_data
.panel
.section_open(PanelSection::FileExplorer),
)
.build()
}
fn file_node_view(
file_node: FileNode,
proxy: ProxyRpcHandler,
level: usize,
/// Initialize the file explorer's naming (renaming, creating, etc.) editor with the given path.
fn initialize_naming_editor_with_path(data: &FileExplorerData, path: &Path) {
let file_name = path.file_name().unwrap_or_default().to_string_lossy();
// Start with the part of the file or directory name before the extension
// selected.
let selection_end = {
let without_leading_dot = file_name.strip_prefix('.').unwrap_or(&file_name);
let idx = without_leading_dot
.find('.')
.unwrap_or(without_leading_dot.len());
idx + file_name.len() - without_leading_dot.len()
};
initialize_naming_editor(data, &file_name, Some(selection_end));
}
fn initialize_naming_editor(
data: &FileExplorerData,
text: &str,
selection_end: Option<usize>,
) {
let text = Rope::from(text);
let selection_end = selection_end.unwrap_or(text.len());
let doc = data.naming_editor_data.doc();
doc.reload(text, true);
data.naming_editor_data
.cursor()
.update(|cursor| cursor.set_insert(Selection::region(0, selection_end)));
data.naming
.update(|naming| naming.set_editor_needs_reset(false));
}
fn file_node_text_color(
config: ReadSignal<Arc<LapceConfig>>,
node: FileNodeViewData,
source_control: SourceControlData,
) -> Color {
let diff = source_control.file_diffs.with(|file_diffs| {
let FileNodeViewKind::Path(path) = &node.kind else {
return None;
};
if node.is_dir {
file_diffs
.keys()
.find(|p| p.as_path().starts_with(path))
.map(|_| FileDiffKind::Modified)
} else {
file_diffs.get(path).map(|(diff, _)| diff.kind())
}
});
let color = match diff {
Some(FileDiffKind::Modified | FileDiffKind::Renamed) => {
LapceColor::SOURCE_CONTROL_MODIFIED
}
Some(FileDiffKind::Added) => LapceColor::SOURCE_CONTROL_ADDED,
Some(FileDiffKind::Deleted) => LapceColor::SOURCE_CONTROL_REMOVED,
None => LapceColor::PANEL_FOREGROUND,
};
config.get().color(color)
}
fn file_node_text_view(
data: FileExplorerData,
node: FileNodeViewData,
source_control: SourceControlData,
) -> impl View {
virtual_list(
VirtualListDirection::Vertical,
VirtualListItemSize::Fn(Box::new(|(_, file_node): &(PathBuf, FileNode)| {
file_node.total_size().unwrap_or(0.0)
})),
move || file_node.clone(),
|(path, _)| path.to_owned(),
move |(path, file_node)| {
let proxy = proxy.clone();
stack(move || {
(
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(),
),
)
}),
),
FileNodeViewKind::Renaming { path, err } => {
if data.naming.with_untracked(Naming::editor_needs_reset) {
initialize_naming_editor_with_path(&data, &path);
}
file_node_input_view(data, err.clone())
}
FileNodeViewKind::Naming { err } => {
if data.naming.with_untracked(Naming::editor_needs_reset) {
initialize_naming_editor(&data, "", None);
}
file_node_input_view(data, err.clone())
}
FileNodeViewKind::Duplicating { source, err } => {
if data.naming.with_untracked(Naming::editor_needs_reset) {
initialize_naming_editor_with_path(&data, &source);
}
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
fn file_node_input_view(data: FileExplorerData, err: Option<String>) -> Container {
let ui_line_height = data.common.ui_line_height;
let naming_editor_data = data.naming_editor_data.clone();
let text_input_file_explorer_data = data.clone();
let focus = data.common.focus;
let config = data.common.config;
let is_focused = move || {
focus.with_untracked(|focus| focus == &Focus::Panel(PanelKind::FileExplorer))
};
let text_input_view = TextInputBuilder::new()
.is_focused(is_focused)
.key_focus(text_input_file_explorer_data)
.build_editor(naming_editor_data.clone())
.on_event_stop(EventListener::FocusLost, move |_| {
data.finish_naming();
data.naming.set(Naming::None);
})
.style(move |s| {
s.width_full()
.height(ui_line_height.get())
.padding(0.0)
.margin(0.0)
.border_radius(6.0)
.border(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
});
let text_input_id = text_input_view.id();
text_input_id.request_focus();
if let Some(err) = err {
container(
stack((
text_input_view,
label(move || err.clone()).style(move |s| {
let config = config.get();
let editor_background_color =
config.color(LapceColor::PANEL_CURRENT_BACKGROUND);
let error_background_color =
config.color(LapceColor::ERROR_LENS_ERROR_BACKGROUND);
let background_color = blend_colors(
editor_background_color,
error_background_color,
);
s.position(Position::Absolute)
.inset_top(ui_line_height.get())
.width_full()
.color(config.color(LapceColor::ERROR_LENS_ERROR_FOREGROUND))
.background(background_color)
.z_index(100)
}),
))
.style(|s| s.flex_grow(1.0)),
)
} else {
container(text_input_view)
}
}
fn new_file_node_view(
data: FileExplorerData,
source_control: SourceControlData,
) -> impl View {
let root = data.root;
let ui_line_height = data.common.ui_line_height;
let config = data.common.config;
let naming = data.naming;
let secondary_click_data = data.clone();
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| {
let level = node.level;
let data = data.clone();
let click_data = data.clone();
let double_click_data = data.clone();
let secondary_click_data = data.clone();
let aux_click_data = data.clone();
let kind = node.kind.clone();
let open = node.open;
let is_dir = node.is_dir;
let view = stack((
svg(move || {
let config = config.get();
let svg_str = match open {
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;
let color = if is_dir {
config.color(LapceColor::LAPCE_ICON_ACTIVE)
} else {
Color::TRANSPARENT
};
s.size(size, size)
.flex_shrink(0.0)
.margin_left(10.0)
.color(color)
}),
{
let file_node = file_node.clone();
let proxy = proxy.clone();
let expanded = file_node.expanded;
let is_dir = file_node.is_dir;
stack(|| {
(
svg(move || {
let config = config.get();
let expanded = expanded.get();
let svg_str = match expanded {
true => LapceIcons::ITEM_OPENED,
false => LapceIcons::ITEM_CLOSED,
};
config.ui_svg(svg_str)
})
.style(move || {
let config = config.get();
let size = config.ui.icon_size() as f32;
let kind = kind.clone();
let kind_for_style = kind.clone();
// TODO: use the current naming input as the path for the file svg
svg(move || {
let config = config.get();
if is_dir {
let svg_str = match open {
true => LapceIcons::DIRECTORY_OPENED,
false => LapceIcons::DIRECTORY_CLOSED,
};
config.ui_svg(svg_str)
} else if let Some(path) = kind.path() {
config.file_svg(path).0
} else {
config.ui_svg(LapceIcons::FILE)
}
})
.style(move |s| {
let config = config.get();
let size = config.ui.icon_size() as f32;
let color = if is_dir {
*config
.get_color(LapceColor::LAPCE_ICON_ACTIVE)
} else {
Color::TRANSPARENT
};
Style::BASE
.size_px(size, size)
.margin_left_px(10.0)
.color(color)
}),
{
let path = path.clone();
let path_for_style = path.clone();
svg(move || {
let config = config.get();
if is_dir {
let expanded = expanded.get();
let svg_str = match expanded {
true => LapceIcons::DIRECTORY_OPENED,
false => {
LapceIcons::DIRECTORY_CLOSED
}
};
config.ui_svg(svg_str)
} else {
config.file_svg(&path).0
}
})
.style(
move || {
let config = config.get();
let size = config.ui.icon_size() as f32;
Style::BASE
.size_px(size, size)
.margin_horiz_px(6.0)
.apply_if(is_dir, |s| {
s.color(*config.get_color(
LapceColor::LAPCE_ICON_ACTIVE,
))
})
.apply_if(!is_dir, |s| {
s.apply_opt(
config
.file_svg(
&path_for_style,
)
.1
.cloned(),
Style::color,
)
})
},
s.size(size, size)
.flex_shrink(0.0)
.margin_horiz(6.0)
.apply_if(is_dir, |s| {
s.color(
config.color(LapceColor::LAPCE_ICON_ACTIVE),
)
},
label(move || {
path.file_name()
.map(|f| f.to_string_lossy().to_string())
.unwrap_or_default()
}),
)
})
.on_click(move |_| {
file_node.click(&proxy);
true
})
.style(move || {
Style::BASE
.items_center()
.padding_right_px(10.0)
.padding_left_px((level * 10) as f32)
.min_width_pct(100.0)
})
.hover_style(move || {
Style::BASE
.background(
*config.get().get_color(
LapceColor::PANEL_HOVERED_BACKGROUND,
),
)
.cursor(CursorStyle::Pointer)
})
.apply_if(!is_dir, |s| {
s.apply_opt(
kind_for_style
.path()
.and_then(|p| config.file_svg(p).1),
Style::color,
)
})
})
},
container_box(move || {
Box::new(file_node_view(
file_node,
proxy.clone(),
level + 1,
config,
))
}),
)
})
.style(|| Style::BASE.flex_col().min_width_pct(100.0))
},
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),
)
.cursor(CursorStyle::Pointer)
})
});
// Only handle click events if we are not naming the file node
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);
})
.on_double_click(move |_| {
double_click_data.double_click(&double_click_path)
})
.on_secondary_click_stop(move |_| {
secondary_click_data.secondary_click(&secondary_click_path);
})
.on_event_stop(
EventListener::PointerDown,
move |event| {
if let Event::PointerDown(pointer_event) = event {
if pointer_event.button.is_auxiliary() {
aux_click_data.middle_click(&aux_click_path);
}
}
},
)
} else {
view
}
},
)
.style(|s| s.flex_col().align_items(AlignItems::Stretch).width_full()),
)
.style(|| Style::BASE.flex_col().min_width_pct(100.0))
.style(|s| s.size_full())
.on_secondary_click_stop(move |_| {
if let Naming::None = naming.get_untracked() {
if let Some(path) = &secondary_click_data.common.workspace.path {
secondary_click_data.secondary_click(path);
}
}
})
}
fn open_editors_view(window_tab_data: Rc<WindowTabData>) -> impl View {
let diff_editors = window_tab_data.main_split.diff_editors;
let editors = window_tab_data.main_split.editors;
let editor_tabs = window_tab_data.main_split.editor_tabs;
let config = window_tab_data.common.config;
let internal_command = window_tab_data.common.internal_command;
let active_editor_tab = window_tab_data.main_split.active_editor_tab;
let plugin = window_tab_data.plugin.clone();
let child_view = move |plugin: PluginData,
editor_tab: RwSignal<EditorTabData>,
child_index: RwSignal<usize>,
child: EditorTabChild| {
let editor_tab_id =
editor_tab.with_untracked(|editor_tab| editor_tab.editor_tab_id);
let child_for_close = child.clone();
let info = child.view_info(editors, diff_editors, plugin, config);
let hovered = create_rw_signal(false);
stack((
clickable_icon(
move || {
if hovered.get() || info.with(|info| info.is_pristine) {
LapceIcons::CLOSE
} else {
LapceIcons::UNSAVED
}
},
move || {
let editor_tab_id =
editor_tab.with_untracked(|t| t.editor_tab_id);
internal_command.send(InternalCommand::EditorTabChildClose {
editor_tab_id,
child: child_for_close.clone(),
});
},
|| false,
|| false,
|| "Close",
config,
)
.on_event_stop(EventListener::PointerEnter, move |_| {
hovered.set(true);
})
.on_event_stop(EventListener::PointerLeave, move |_| {
hovered.set(false);
})
.on_event_stop(EventListener::PointerDown, |_| {})
.style(|s| s.margin_left(10.0)),
container(svg(move || info.with(|info| info.icon.clone())).style(
move |s| {
let size = config.get().ui.icon_size() as f32;
s.size(size, size)
.apply_opt(info.with(|info| info.color), |s, c| s.color(c))
},
))
.style(|s| s.padding_horiz(6.0)),
label(move || info.with(|info| info.path.clone())).style(move |s| {
s.apply_if(
!info
.with(|info| info.confirmed)
.map(|confirmed| confirmed.get())
.unwrap_or(true),
|s| s.font_style(FontStyle::Italic),
)
}),
))
.style(move |s| {
let config = config.get();
s.items_center()
.width_pct(100.0)
.apply_if(
active_editor_tab.get() == Some(editor_tab_id)
&& editor_tab.with(|editor_tab| editor_tab.active)
== child_index.get(),
|s| {
s.background(
config.color(LapceColor::PANEL_CURRENT_BACKGROUND),
)
},
)
.hover(|s| {
s.background(config.color(LapceColor::PANEL_HOVERED_BACKGROUND))
})
})
.on_event_cont(EventListener::PointerDown, move |_| {
editor_tab.update(|editor_tab| {
editor_tab.active = child_index.get_untracked();
});
active_editor_tab.set(Some(editor_tab_id));
})
};
scroll(
dyn_stack(
move || editor_tabs.get().into_iter().enumerate(),
move |(index, (editor_tab_id, _))| (*index, *editor_tab_id),
move |(index, (_, editor_tab))| {
let plugin = plugin.clone();
stack((
label(move || format!("Group {}", index + 1))
.style(|s| s.margin_left(10.0)),
dyn_stack(
move || editor_tab.get().children,
move |(_, _, child)| child.id(),
move |(child_index, _, child)| {
child_view(
plugin.clone(),
editor_tab,
child_index,
child,
)
},
)
.style(|s| s.flex_col().width_pct(100.0)),
))
.style(|s| s.flex_col())
},
)
.style(|s| s.flex_col().width_pct(100.0)),
)
.style(|s| s.absolute().size_pct(100.0, 100.0).line_height(1.6))
}
+105 -13
View File
@@ -59,6 +59,7 @@ pub struct FindSearchString {
#[derive(Clone)]
pub struct Find {
pub rev: RwSignal<u64>,
/// If the find is shown
pub visual: RwSignal<bool>,
/// The currently active search string.
@@ -78,6 +79,7 @@ pub struct Find {
impl Find {
pub fn new(cx: Scope) -> Self {
let find = Self {
rev: cx.create_rw_signal(0),
visual: cx.create_rw_signal(false),
search_string: cx.create_rw_signal(None),
case_matching: cx.create_rw_signal(CaseMatching::CaseInsensitive),
@@ -104,6 +106,18 @@ impl Find {
});
}
{
let find = find.clone();
cx.create_effect(move |_| {
find.search_string.track();
find.case_matching.track();
find.whole_words.track();
find.rev.update(|rev| {
*rev += 1;
});
});
}
find
}
@@ -201,7 +215,7 @@ impl Find {
let end = find_cursor.pos();
if whole_words
&& !self.is_matching_whole_words(text, start, end)
&& !Self::is_matching_whole_words(text, start, end)
{
raw_lines =
text.lines_raw(find_cursor.pos()..text.len());
@@ -226,7 +240,7 @@ impl Find {
let end = find_cursor.pos();
if whole_words
&& !self.is_matching_whole_words(text, start, end)
&& !Self::is_matching_whole_words(text, start, end)
{
raw_lines =
text.lines_raw(find_cursor.pos()..offset);
@@ -249,7 +263,7 @@ impl Find {
let end = find_cursor.pos();
raw_lines = text.lines_raw(find_cursor.pos()..offset);
if whole_words
&& !self.is_matching_whole_words(text, start, end)
&& !Self::is_matching_whole_words(text, start, end)
{
continue;
}
@@ -274,7 +288,7 @@ impl Find {
let end = find_cursor.pos();
if whole_words
&& !self.is_matching_whole_words(text, start, end)
&& !Self::is_matching_whole_words(text, start, end)
{
raw_lines =
text.lines_raw(find_cursor.pos()..text.len());
@@ -298,12 +312,7 @@ impl Find {
}
/// Checks if the start and end of a match is matching whole words.
fn is_matching_whole_words(
&self,
text: &Rope,
start: usize,
end: usize,
) -> bool {
fn is_matching_whole_words(text: &Rope, start: usize, end: usize) -> bool {
let mut word_end_cursor = WordCursor::new(text, end - 1);
let mut word_start_cursor = WordCursor::new(text, start + 1);
@@ -319,7 +328,7 @@ impl Find {
}
/// Returns `true` if the search query is a multi-line regex.
pub(crate) fn is_multiline_regex(&self) -> bool {
pub fn is_multiline_regex(&self) -> bool {
self.search_string.with_untracked(|search| {
if let Some(search) = search.as_ref() {
search.regex.is_some() && is_multiline_regex(&search.content)
@@ -329,6 +338,88 @@ impl Find {
})
}
#[allow(clippy::too_many_arguments)]
pub fn find(
text: &Rope,
search: &FindSearchString,
start: usize,
end: usize,
case_matching: CaseMatching,
whole_words: bool,
include_slop: bool,
occurrences: &mut Selection,
) {
let search_string = &search.content;
let slop = if include_slop {
search.content.len() * 2
} else {
0
};
// expand region to be able to find occurrences around the region's edges
let expanded_start = max(start, slop) - slop;
let expanded_end = min(end + slop, text.len());
let from = text
.at_or_prev_codepoint_boundary(expanded_start)
.unwrap_or(0);
let to = text
.at_or_next_codepoint_boundary(expanded_end)
.unwrap_or_else(|| text.len());
let mut to_cursor = Cursor::new(text, to);
let _ = to_cursor.next_leaf();
let sub_text = text.subseq(Interval::new(0, to_cursor.pos()));
let mut find_cursor = Cursor::new(&sub_text, from);
let mut raw_lines = text.lines_raw(from..to);
while let Some(start) = find(
&mut find_cursor,
&mut raw_lines,
case_matching,
search_string,
search.regex.as_ref(),
) {
let end = find_cursor.pos();
if whole_words && !Self::is_matching_whole_words(text, start, end) {
raw_lines = text.lines_raw(find_cursor.pos()..to);
continue;
}
let region = SelRegion::new(start, end, None);
let (_, e) = occurrences.add_range_distinct(region);
// in case of ambiguous search results (e.g. search "aba" in "ababa"),
// the search result closer to the beginning of the file wins
if e != end {
// Skip the search result and keep the occurrence that is closer to
// the beginning of the file. Re-align the cursor to the kept
// occurrence
find_cursor.set(e);
raw_lines = text.lines_raw(find_cursor.pos()..to);
continue;
}
// in case current cursor matches search result (for example query a* matches)
// all cursor positions, then cursor needs to be increased so that search
// continues at next position. Otherwise, search will result in overflow since
// search will always repeat at current cursor position.
if start == end {
// determine whether end of text is reached and stop search or increase
// cursor manually
if end + 1 >= text.len() {
break;
} else {
find_cursor.set(end + 1);
}
}
// update line iterator so that line starts at current cursor position
raw_lines = text.lines_raw(find_cursor.pos()..to);
}
}
/// Execute the search on the provided text in the range provided by `start` and `end`.
pub fn update_find(
&self,
@@ -380,7 +471,7 @@ impl Find {
) {
let end = find_cursor.pos();
if whole_words && !self.is_matching_whole_words(text, start, end) {
if whole_words && !Self::is_matching_whole_words(text, start, end) {
raw_lines = text.lines_raw(find_cursor.pos()..to);
continue;
}
@@ -420,6 +511,7 @@ impl Find {
#[derive(Clone)]
pub struct FindResult {
pub find_rev: RwSignal<u64>,
pub progress: RwSignal<FindProgress>,
pub occurrences: RwSignal<Selection>,
pub search_string: RwSignal<Option<FindSearchString>>,
@@ -431,6 +523,7 @@ pub struct FindResult {
impl FindResult {
pub fn new(cx: Scope) -> Self {
Self {
find_rev: cx.create_rw_signal(0),
progress: cx.create_rw_signal(FindProgress::Started),
occurrences: cx.create_rw_signal(Selection::new()),
search_string: cx.create_rw_signal(None),
@@ -442,6 +535,5 @@ impl FindResult {
pub fn reset(&self) {
self.progress.set(FindProgress::Started);
self.occurrences.set(Selection::new());
}
}
+63 -99
View File
@@ -1,19 +1,25 @@
use floem::{
cosmic_text::{
Attrs, AttrsList, FamilyOwned, LineHeightValue, TextLayout, Weight,
},
id::Id,
cosmic_text::{Attrs, AttrsList, FamilyOwned, TextLayout, Weight},
peniko::{
kurbo::{Point, Rect},
Color,
},
prop_extractor,
reactive::create_effect,
style::{ComputedStyle, Style},
taffy::prelude::Node,
view::{ChangeFlags, View},
Renderer, ViewContext,
style::{FontFamily, FontSize, LineHeight, Style, TextColor},
taffy::prelude::NodeId,
Renderer, View, ViewId,
};
prop_extractor! {
Extractor {
color: TextColor,
font_size: FontSize,
font_family: FontFamily,
line_height: LineHeight,
}
}
enum FocusTextState {
Text(String),
FocusColor(Color),
@@ -25,65 +31,57 @@ pub fn focus_text(
focus_indices: impl Fn() -> Vec<usize> + 'static,
focus_color: impl Fn() -> Color + 'static,
) -> FocusText {
let cx = ViewContext::get_current();
let id = cx.new_id();
let id = ViewId::new();
create_effect(move |_| {
let new_text = text();
id.update_state(FocusTextState::Text(new_text), false);
id.update_state(FocusTextState::Text(new_text));
});
create_effect(move |_| {
let focus_color = focus_color();
id.update_state(FocusTextState::FocusColor(focus_color), false);
id.update_state(FocusTextState::FocusColor(focus_color));
});
create_effect(move |_| {
let focus_indices = focus_indices();
id.update_state(FocusTextState::FocusIndices(focus_indices), false);
id.update_state(FocusTextState::FocusIndices(focus_indices));
});
FocusText {
id,
text: "".to_string(),
text_layout: None,
color: None,
focus_color: Color::default(),
focus_indices: Vec::new(),
text_node: None,
font_size: None,
font_family: None,
line_height: None,
available_text: None,
available_width: None,
available_text_layout: None,
style: Default::default(),
}
}
pub struct FocusText {
id: Id,
id: ViewId,
text: String,
text_layout: Option<TextLayout>,
color: Option<Color>,
focus_color: Color,
focus_indices: Vec<usize>,
font_size: Option<f32>,
font_family: Option<String>,
line_height: Option<LineHeightValue>,
text_node: Option<Node>,
text_node: Option<NodeId>,
available_text: Option<String>,
available_width: Option<f32>,
available_text_layout: Option<TextLayout>,
style: Extractor,
}
impl FocusText {
fn set_text_layout(&mut self) {
let mut attrs = Attrs::new().color(self.color.unwrap_or_default());
if let Some(font_size) = self.font_size {
let mut attrs = Attrs::new().color(self.style.color().unwrap_or_default());
if let Some(font_size) = self.style.font_size() {
attrs = attrs.font_size(font_size);
}
let font_family = self.font_family.as_ref().map(|font_family| {
let font_family = self.style.font_family().as_ref().map(|font_family| {
let family: Vec<FamilyOwned> =
FamilyOwned::parse_list(font_family).collect();
family
@@ -91,7 +89,7 @@ impl FocusText {
if let Some(font_family) = font_family.as_ref() {
attrs = attrs.family(font_family);
}
if let Some(line_height) = self.line_height {
if let Some(line_height) = self.style.line_height() {
attrs = attrs.line_height(line_height);
}
@@ -120,11 +118,12 @@ impl FocusText {
if let Some(new_text) = self.available_text.as_ref() {
let new_text_len = new_text.len();
let mut attrs = Attrs::new().color(self.color.unwrap_or_default());
if let Some(font_size) = self.font_size {
let mut attrs =
Attrs::new().color(self.style.color().unwrap_or_default());
if let Some(font_size) = self.style.font_size() {
attrs = attrs.font_size(font_size);
}
let font_family = self.font_family.as_ref().map(|font_family| {
let font_family = self.style.font_family().as_ref().map(|font_family| {
let family: Vec<FamilyOwned> =
FamilyOwned::parse_list(font_family).collect();
family
@@ -162,31 +161,15 @@ impl FocusText {
}
impl View for FocusText {
fn id(&self) -> floem::id::Id {
fn id(&self) -> ViewId {
self.id
}
fn child(&self, _id: floem::id::Id) -> Option<&dyn View> {
None
}
fn child_mut(&mut self, _id: floem::id::Id) -> Option<&mut dyn View> {
None
}
fn children(&self) -> Vec<&dyn View> {
Vec::new()
}
fn children_mut(&mut self) -> Vec<&mut dyn View> {
Vec::new()
}
fn update(
&mut self,
cx: &mut floem::context::UpdateCx,
_cx: &mut floem::context::UpdateCx,
state: Box<dyn std::any::Any>,
) -> floem::view::ChangeFlags {
) {
if let Ok(state) = state.downcast() {
match *state {
FocusTextState::Text(text) => {
@@ -200,68 +183,67 @@ impl View for FocusText {
}
}
self.set_text_layout();
cx.request_layout(self.id());
ChangeFlags::LAYOUT
} else {
ChangeFlags::empty()
self.id.request_layout();
}
}
fn style(&mut self, cx: &mut floem::context::StyleCx<'_>) {
if self.style.read(cx) {
self.set_text_layout();
self.id.request_layout();
}
}
fn layout(
&mut self,
cx: &mut floem::context::LayoutCx,
) -> floem::taffy::prelude::Node {
cx.layout_node(self.id, true, |cx| {
if self.font_size != cx.current_font_size()
|| self.font_family.as_deref() != cx.current_font_family()
|| self.line_height != cx.current_line_height()
{
self.font_size = cx.current_font_size();
self.font_family = cx.current_font_family().map(|s| s.to_string());
self.line_height = cx.current_line_height();
self.set_text_layout();
}
) -> floem::taffy::prelude::NodeId {
cx.layout_node(self.id, true, |_cx| {
if self.text_layout.is_none() {
self.set_text_layout();
}
let text_layout = self.text_layout.as_ref().unwrap();
let size = text_layout.size();
let width = size.width.ceil() as f32;
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::BASE
.size_px(width, height)
.compute(&ComputedStyle::default())
.to_taffy_style();
cx.set_style(text_node, style);
let style = Style::new().width(width).height(height).to_taffy_style();
self.id.set_taffy_style(text_node, style);
vec![text_node]
})
}
fn compute_layout(&mut self, cx: &mut floem::context::LayoutCx) -> Option<Rect> {
fn compute_layout(
&mut self,
_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 {
if self.available_width != Some(layout.size.width) {
let mut dots_text = TextLayout::new();
let mut attrs = Attrs::new().color(
self.color.unwrap_or_else(|| Color::rgb8(0xf0, 0xf0, 0xea)),
self.style
.color()
.unwrap_or_else(|| Color::rgb8(0xf0, 0xf0, 0xea)),
);
if let Some(font_size) = self.font_size {
if let Some(font_size) = self.style.font_size() {
attrs = attrs.font_size(font_size);
}
let font_family = self.font_family.as_ref().map(|font_family| {
let family: Vec<FamilyOwned> =
FamilyOwned::parse_list(font_family).collect();
family
});
let font_family =
self.style.font_family().as_ref().map(|font_family| {
let family: Vec<FamilyOwned> =
FamilyOwned::parse_list(font_family).collect();
family
});
if let Some(font_family) = font_family.as_ref() {
attrs = attrs.family(font_family);
}
@@ -291,27 +273,9 @@ impl View for FocusText {
None
}
fn event(
&mut self,
_cx: &mut floem::context::EventCx,
_id_path: Option<&[floem::id::Id]>,
_event: floem::event::Event,
) -> bool {
false
}
fn paint(&mut self, cx: &mut floem::context::PaintCx) {
if self.color != cx.current_color()
|| self.font_size != cx.current_font_size()
|| self.font_family.as_deref() != cx.current_font_family()
{
self.color = cx.current_color();
self.font_size = cx.current_font_size();
self.font_family = cx.current_font_family().map(|s| s.to_string());
self.set_text_layout();
}
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);
+25 -34
View File
@@ -1,9 +1,10 @@
use std::{ops::Range, path::PathBuf};
use std::{ops::Range, path::PathBuf, rc::Rc};
use floem::{
ext_event::create_ext_action,
keyboard::Modifiers,
reactive::{Memo, RwSignal, Scope},
views::VirtualListVector,
views::VirtualVector,
};
use indexmap::IndexMap;
use lapce_core::{mode::Mode, selection::Selection};
@@ -13,7 +14,6 @@ use lapce_xi_rope::Rope;
use crate::{
command::{CommandExecuted, CommandKind},
editor::EditorData,
id::EditorId,
keypress::{condition::Condition, KeyPressFocus},
main_split::MainSplitData,
window_tab::CommonData,
@@ -43,7 +43,7 @@ pub struct GlobalSearchData {
pub editor: EditorData,
pub search_result: RwSignal<IndexMap<PathBuf, SearchMatchData>>,
pub main_split: MainSplitData,
pub common: CommonData,
pub common: Rc<CommonData>,
}
impl KeyPressFocus for GlobalSearchData {
@@ -59,19 +59,20 @@ impl KeyPressFocus for GlobalSearchData {
&self,
command: &crate::command::LapceCommand,
count: Option<usize>,
mods: floem::glazier::Modifiers,
mods: Modifiers,
) -> CommandExecuted {
match &command.kind {
CommandKind::Workbench(_) => {}
CommandKind::Scroll(_) => {}
CommandKind::Focus(_) => {}
CommandKind::Edit(_)
| CommandKind::Move(_)
| CommandKind::MultiSelection(_) => {
self.editor.run_command(command, count, mods);
return self.editor.run_command(command, count, mods);
}
CommandKind::MotionMode(_) => {}
}
CommandExecuted::Yes
CommandExecuted::No
}
fn receive_char(&self, c: &str) {
@@ -79,16 +80,9 @@ impl KeyPressFocus for GlobalSearchData {
}
}
impl VirtualListVector<(PathBuf, SearchMatchData)> for GlobalSearchData {
type ItemIterator = Box<dyn Iterator<Item = (PathBuf, SearchMatchData)>>;
impl VirtualVector<(PathBuf, SearchMatchData)> for GlobalSearchData {
fn total_len(&self) -> usize {
0
}
fn total_size(&self) -> Option<f64> {
let line_height = self.common.ui_line_height.get();
let count: usize = self.search_result.with(|result| {
self.search_result.with(|result| {
result
.iter()
.map(|(_, data)| {
@@ -99,18 +93,21 @@ impl VirtualListVector<(PathBuf, SearchMatchData)> for GlobalSearchData {
}
})
.sum()
});
Some(line_height * count as f64)
})
}
fn slice(&mut self, _range: Range<usize>) -> Self::ItemIterator {
Box::new(self.search_result.get().into_iter())
fn slice(
&mut self,
_range: Range<usize>,
) -> impl Iterator<Item = (PathBuf, SearchMatchData)> {
self.search_result.get().into_iter()
}
}
impl GlobalSearchData {
pub fn new(cx: Scope, main_split: MainSplitData, common: CommonData) -> Self {
let editor = EditorData::new_local(cx, EditorId::next(), common.clone());
pub fn new(cx: Scope, main_split: MainSplitData) -> Self {
let common = main_split.common.clone();
let editor = main_split.editors.make_local(cx, common.clone());
let search_result = cx.create_rw_signal(IndexMap::new());
let global_search = Self {
@@ -122,12 +119,9 @@ impl GlobalSearchData {
{
let global_search = global_search.clone();
let buffer = global_search.editor.doc().buffer;
cx.create_effect(move |_| {
let pattern = global_search
.editor
.view
.doc
.with(|doc| doc.buffer().to_string());
let pattern = buffer.with(|buffer| buffer.to_string());
if pattern.is_empty() {
global_search.search_result.update(|r| r.clear());
return;
@@ -158,10 +152,10 @@ impl GlobalSearchData {
}
{
let global_search_doc = global_search.editor.view.doc;
let buffer = global_search.editor.doc().buffer;
let main_split = global_search.main_split.clone();
cx.create_effect(move |_| {
let content = global_search_doc.with(|doc| doc.buffer().to_string());
let content = buffer.with(|buffer| buffer.to_string());
main_split.set_find_pattern(Some(content));
});
}
@@ -198,12 +192,9 @@ impl GlobalSearchData {
pub fn set_pattern(&self, pattern: String) {
let pattern_len = pattern.len();
self.editor.doc().reload(Rope::from(pattern), true);
self.editor
.view
.doc
.update(|doc| doc.reload(Rope::from(pattern), true));
self.editor
.cursor
.cursor()
.update(|cursor| cursor.set_insert(Selection::region(0, pattern_len)));
}
}
+28
View File
@@ -0,0 +1,28 @@
use floem::{
peniko::kurbo::Rect,
reactive::{RwSignal, Scope},
views::editor::id::EditorId,
};
use crate::markdown::MarkdownContent;
#[derive(Clone)]
pub struct HoverData {
pub active: RwSignal<bool>,
pub offset: RwSignal<usize>,
pub editor_id: RwSignal<EditorId>,
pub content: RwSignal<Vec<MarkdownContent>>,
pub layout_rect: RwSignal<Rect>,
}
impl HoverData {
pub fn new(cx: Scope) -> Self {
Self {
active: cx.create_rw_signal(false),
offset: cx.create_rw_signal(0),
content: cx.create_rw_signal(Vec::new()),
editor_id: cx.create_rw_signal(EditorId::next()),
layout_rect: cx.create_rw_signal(Rect::ZERO),
}
}
}
+3 -16
View File
@@ -1,24 +1,11 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Id(u64);
impl Id {
/// Allocate a new, unique `Id`.
pub fn next() -> Id {
use floem::glazier::Counter;
static ID_COUNTER: Counter = Counter::new();
Id(ID_COUNTER.next())
}
pub fn to_raw(self) -> u64 {
self.0
}
}
use floem::views::editor::id::Id;
pub type SplitId = Id;
pub type WindowTabId = Id;
pub type EditorTabId = Id;
pub type SettingsId = Id;
pub type KeymapId = Id;
pub type EditorId = Id;
pub type ThemeColorSettingsId = Id;
pub type VoltViewId = Id;
pub type DiffEditorId = Id;
pub type TerminalTabId = Id;
+288
View File
@@ -0,0 +1,288 @@
use std::{borrow::Cow, ops::Range, path::PathBuf, str::FromStr};
use floem::reactive::{batch, RwSignal, Scope};
use lapce_core::{
buffer::{
rope_text::{RopeText, RopeTextRef},
Buffer,
},
rope_text_pos::RopeTextPosition,
selection::Selection,
};
use lsp_types::InsertTextFormat;
use crate::{config::LapceConfig, doc::Doc, editor::EditorData, snippet::Snippet};
// TODO: we could integrate completion lens with this, so it is considered at the same time
/// Redefinition of lsp types inline completion item with offset range
#[derive(Debug, Clone)]
pub struct InlineCompletionItem {
/// The text to replace the range with.
pub insert_text: String,
/// Text used to decide if this inline completion should be shown.
pub filter_text: Option<String>,
/// The range (of offsets) to replace
pub range: Option<Range<usize>>,
pub command: Option<lsp_types::Command>,
pub insert_text_format: Option<InsertTextFormat>,
}
impl InlineCompletionItem {
pub fn from_lsp(buffer: &Buffer, item: lsp_types::InlineCompletionItem) -> Self {
let range = item.range.map(|r| {
let start = buffer.offset_of_position(&r.start);
let end = buffer.offset_of_position(&r.end);
start..end
});
Self {
insert_text: item.insert_text,
filter_text: item.filter_text,
range,
command: item.command,
insert_text_format: item.insert_text_format,
}
}
pub fn apply(
&self,
editor: &EditorData,
start_offset: usize,
) -> anyhow::Result<()> {
let text_format = self
.insert_text_format
.unwrap_or(InsertTextFormat::PLAIN_TEXT);
let selection = if let Some(range) = &self.range {
Selection::region(range.start, range.end)
} else {
Selection::caret(start_offset)
};
match text_format {
InsertTextFormat::PLAIN_TEXT => editor.do_edit(
&selection,
&[(selection.clone(), self.insert_text.as_str())],
),
InsertTextFormat::SNIPPET => {
editor.completion_apply_snippet(
&self.insert_text,
&selection,
Vec::new(),
start_offset,
)?;
}
_ => {
// We don't know how to support this text format
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InlineCompletionStatus {
/// The inline completion is not active.
Inactive,
/// The inline completion is active and is waiting for the server to respond.
Started,
/// The inline completion is active and has received a response from the server.
Active,
}
#[derive(Clone)]
pub struct InlineCompletionData {
pub status: InlineCompletionStatus,
/// The active inline completion index in the list of completions.
pub active: RwSignal<usize>,
pub items: im::Vector<InlineCompletionItem>,
pub start_offset: usize,
pub path: PathBuf,
}
impl InlineCompletionData {
pub fn new(cx: Scope) -> Self {
Self {
status: InlineCompletionStatus::Inactive,
active: cx.create_rw_signal(0),
items: im::vector![],
start_offset: 0,
path: PathBuf::new(),
}
}
pub fn current_item(&self) -> Option<&InlineCompletionItem> {
let active = self.active.get_untracked();
self.items.get(active)
}
pub fn next(&mut self) {
if !self.items.is_empty() {
let next_index = (self.active.get_untracked() + 1) % self.items.len();
self.active.set(next_index);
}
}
pub fn previous(&mut self) {
if !self.items.is_empty() {
let prev_index = if self.active.get_untracked() == 0 {
self.items.len() - 1
} else {
self.active.get_untracked() - 1
};
self.active.set(prev_index);
}
}
pub fn cancel(&mut self) {
if self.status == InlineCompletionStatus::Inactive {
return;
}
self.items.clear();
self.status = InlineCompletionStatus::Inactive;
}
/// Set the items for the inline completion.
/// Sets `active` to `0` and `status` to `InlineCompletionStatus::Active`.
pub fn set_items(
&mut self,
items: im::Vector<InlineCompletionItem>,
start_offset: usize,
path: PathBuf,
) {
batch(|| {
self.items = items;
self.active.set(0);
self.status = InlineCompletionStatus::Active;
self.start_offset = start_offset;
self.path = path;
});
}
pub fn update_doc(&self, doc: &Doc, offset: usize) {
if self.status != InlineCompletionStatus::Active {
doc.clear_inline_completion();
return;
}
if self.items.is_empty() {
doc.clear_inline_completion();
return;
}
let active = self.active.get_untracked();
let active = if active >= self.items.len() {
self.active.set(0);
0
} else {
active
};
let item = &self.items[active];
let text = item.insert_text.clone();
// TODO: is range really meant to be used for this?
let offset = item.range.as_ref().map(|r| r.start).unwrap_or(offset);
let (line, col) = doc
.buffer
.with_untracked(|buffer| buffer.offset_to_line_col(offset));
doc.set_inline_completion(text, line, col);
}
pub fn update_inline_completion(
&self,
config: &LapceConfig,
doc: &Doc,
cursor_offset: usize,
) {
if !config.editor.enable_inline_completion {
doc.clear_inline_completion();
return;
}
let text = doc.buffer.with_untracked(|buffer| buffer.text().clone());
let text = RopeTextRef::new(&text);
let Some(item) = self.current_item() else {
// TODO(minor): should we cancel completion
return;
};
let completion = doc.inline_completion.with_untracked(|cur| {
let cur = cur.as_deref();
inline_completion_text(text, self.start_offset, cursor_offset, item, cur)
});
match completion {
ICompletionRes::Hide => {
doc.clear_inline_completion();
}
ICompletionRes::Unchanged => {}
ICompletionRes::Set(new, shift) => {
let offset = self.start_offset + shift;
let (line, col) = text.offset_to_line_col(offset);
doc.set_inline_completion(new, line, col);
}
}
}
}
enum ICompletionRes {
Hide,
Unchanged,
Set(String, usize),
}
/// Get the text of the inline completion item
fn inline_completion_text(
rope_text: impl RopeText,
start_offset: usize,
cursor_offset: usize,
item: &InlineCompletionItem,
current_completion: Option<&str>,
) -> ICompletionRes {
let text_format = item
.insert_text_format
.unwrap_or(InsertTextFormat::PLAIN_TEXT);
// TODO: is this check correct? I mostly copied it from completion lens
let cursor_prev_offset = rope_text.prev_code_boundary(cursor_offset);
if let Some(range) = &item.range {
let edit_start = range.start;
// If the start of the edit isn't where the cursor currently is, and is not at the start of
// the inline completion, then we ignore it.
if cursor_prev_offset != edit_start && start_offset != edit_start {
return ICompletionRes::Hide;
}
}
let text = match text_format {
InsertTextFormat::PLAIN_TEXT => Cow::Borrowed(&item.insert_text),
InsertTextFormat::SNIPPET => {
let Ok(snippet) = Snippet::from_str(&item.insert_text) else {
return ICompletionRes::Hide;
};
let text = snippet.text();
Cow::Owned(text)
}
_ => {
// We don't know how to support this text format
return ICompletionRes::Hide;
}
};
let range = start_offset..rope_text.offset_line_end(start_offset, true);
let prefix = rope_text.slice_to_cow(range);
// We strip the prefix of the current input from the label.
// So that, for example `p` with a completion of `println` will show `rintln`.
let Some(text) = text.strip_prefix(prefix.as_ref()) else {
return ICompletionRes::Hide;
};
if Some(text) == current_completion {
ICompletionRes::Unchanged
} else {
ICompletionRes::Set(text.to_string(), prefix.len())
}
}
+419 -472
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{rc::Rc, sync::Arc};
use floem::{
event::{Event, EventListener},
@@ -6,22 +6,24 @@ use floem::{
create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal,
Scope,
},
style::{CursorStyle, Style},
view::View,
style::CursorStyle,
views::{
container, label, list, scroll, stack, virtual_list, Decorators,
VirtualListDirection, VirtualListItemSize,
container, dyn_stack, label, scroll, stack, text, virtual_stack, Decorators,
VirtualDirection, VirtualItemSize,
},
View,
};
use lapce_core::mode::Modes;
use crate::{
command::LapceCommand,
config::{color::LapceColor, LapceConfig},
editor::EditorData,
id::EditorId,
keypress::{keymap::KeyMap, KeyPress, KeyPressData},
text_input::text_input,
keypress::{
keymap::{KeyMap, KeyMapPress},
KeyPressData,
},
main_split::Editors,
text_input::TextInputBuilder,
window_tab::CommonData,
};
@@ -29,13 +31,14 @@ use crate::{
pub struct KeymapPicker {
cmd: RwSignal<Option<LapceCommand>>,
keymap: RwSignal<Option<KeyMap>>,
keys: RwSignal<Vec<KeyPress>>,
keys: RwSignal<Vec<(KeyMapPress, bool)>>,
}
pub fn keymap_view(common: CommonData) -> impl View {
pub fn keymap_view(editors: Editors, common: Rc<CommonData>) -> impl View {
let config = common.config;
let keypress = common.keypress;
let ui_line_height = move || common.ui_line_height.get() * 1.2;
let ui_line_height_memo = common.ui_line_height;
let ui_line_height = move || ui_line_height_memo.get() * 1.2;
let modal = create_memo(move |_| config.get().core.modal);
let picker = KeymapPicker {
cmd: create_rw_signal(None),
@@ -44,28 +47,45 @@ pub fn keymap_view(common: CommonData) -> impl View {
};
let cx = Scope::current();
let editor = EditorData::new_local(cx, EditorId::next(), common.clone());
let doc = editor.view.doc;
let text_input_view = TextInputBuilder::new().build(cx, editors, common.clone());
let doc = text_input_view.doc_signal();
let items = move || {
let pattern = doc.with(|doc| doc.buffer().to_string().to_lowercase());
let doc = doc.get();
let pattern = doc.buffer.with(|b| b.to_string().to_lowercase());
let keypress = keypress.get();
let mut items = keypress
.commands_with_keymap
.iter()
.filter_map(|keymap| {
let cmd = keypress.commands.get(&keymap.command).cloned()?;
let match_pattern =
cmd.kind.str().replace('_', " ").contains(&pattern)
|| cmd
.kind
.desc()
.map(|desc| desc.to_lowercase().contains(&pattern))
.unwrap_or(false);
if !match_pattern {
return None;
let cmd_name_contains_pattern =
cmd.kind.str().replace('_', " ").contains(&pattern);
let cmd_desc_contains_pattern = cmd
.kind
.desc()
.map(|desc| desc.to_lowercase().contains(&pattern))
.unwrap_or(false);
let shortcut_contains_pattern = keymap
.key
.iter()
.any(|k| k.label().trim().to_lowercase().contains(&pattern));
let when_contains_pattern = keymap
.when
.as_ref()
.map(|when| when.to_lowercase().contains(&pattern))
.unwrap_or(false);
if cmd_name_contains_pattern
|| cmd_desc_contains_pattern
|| shortcut_contains_pattern
|| when_contains_pattern
{
Some((cmd, Some(keymap.clone())))
} else {
None
}
Some((cmd, Some(keymap.clone())))
})
.collect::<im::Vector<(LapceCommand, Option<KeyMap>)>>();
items.extend(keypress.commands_without_keymap.iter().filter_map(|cmd| {
@@ -90,168 +110,146 @@ pub fn keymap_view(common: CommonData) -> impl View {
move |(i, (cmd, keymap)): (usize, (LapceCommand, Option<KeyMap>))| {
let local_keymap = keymap.clone();
let local_cmd = cmd.clone();
stack(|| {
(
container(|| {
label(move || {
cmd.kind
.desc()
.map(|desc| desc.to_string())
.unwrap_or_else(|| cmd.kind.str().replace('_', " "))
})
.style(|| {
Style::BASE
.text_ellipsis()
.absolute()
.items_center()
.min_width_px(0.0)
.padding_horiz_px(10.0)
.size_pct(100.0, 100.0)
})
})
.style(move || {
Style::BASE
.height_pct(100.0)
.min_width_px(0.0)
.flex_basis_px(0.0)
.flex_grow(1.0)
.border_right(1.0)
.border_color(
*config.get().get_color(LapceColor::LAPCE_BORDER),
)
}),
{
let keymap = keymap.clone();
list(
move || {
keymap
.as_ref()
.map(|keymap| {
keymap
.key
.iter()
.map(|key| {
key.label().trim().to_string()
})
.filter(|l| !l.is_empty())
.collect::<Vec<String>>()
})
.unwrap_or_default()
},
|k| k.clone(),
move |key| {
label(move || key.clone()).style(move || {
Style::BASE
.padding_horiz_px(5.0)
.padding_vert_px(1.0)
.margin_right_px(5.0)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
},
)
.style(move || {
Style::BASE
.items_center()
.padding_horiz_px(10.0)
.min_width_px(200.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
},
{
let keymap = keymap.clone();
let bits = [
(Modes::INSERT, "Insert"),
(Modes::NORMAL, "Normal"),
(Modes::VISUAL, "Visual"),
(Modes::TERMINAL, "Terminal"),
];
let modes = keymap
.as_ref()
.map(|keymap| {
bits.iter()
.filter_map(|(bit, mode)| {
if keymap.modes.contains(*bit) {
Some(mode.to_string())
} else {
None
}
})
.collect::<Vec<String>>()
})
.unwrap_or_default();
list(
move || modes.clone(),
|m| m.clone(),
move |mode| {
label(move || mode.clone()).style(move || {
Style::BASE
.padding_horiz_px(5.0)
.padding_vert_px(1.0)
.margin_right_px(5.0)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
},
)
.style(move || {
Style::BASE
.items_center()
.padding_horiz_px(10.0)
.min_width_px(200.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
.apply_if(!modal.get(), |s| s.hide())
})
},
container(|| {
label(move || {
keymap
.as_ref()
.and_then(|keymap| keymap.when.clone())
.unwrap_or_default()
})
.style(|| {
Style::BASE
.text_ellipsis()
.absolute()
.items_center()
.min_width_px(0.0)
.padding_horiz_px(10.0)
.size_pct(100.0, 100.0)
})
})
.style(move || {
Style::BASE
.height_pct(100.0)
.min_width_px(0.0)
.flex_basis_px(0.0)
.flex_grow(1.0)
stack((
container(
text(
cmd.kind
.desc()
.map(|desc| desc.to_string())
.unwrap_or_else(|| cmd.kind.str().replace('_', " ")),
)
.style(|s| {
s.text_ellipsis()
.absolute()
.items_center()
.min_width(0.0)
.padding_horiz(10.0)
.size_pct(100.0, 100.0)
}),
)
})
.on_click(move |_| {
.style(move |s| {
s.height_pct(100.0)
.min_width(0.0)
.flex_basis(0.0)
.flex_grow(1.0)
.border_right(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
}),
{
let keymap = keymap.clone();
dyn_stack(
move || {
keymap
.as_ref()
.map(|keymap| {
keymap
.key
.iter()
.map(|key| key.label())
.filter(|l| !l.is_empty())
.collect::<Vec<String>>()
})
.unwrap_or_default()
},
|k| k.clone(),
move |key| {
text(key.clone()).style(move |s| {
s.padding_horiz(5.0)
.padding_vert(1.0)
.margin_right(5.0)
.border(1.0)
.border_radius(3.0)
.border_color(
config.get().color(LapceColor::LAPCE_BORDER),
)
})
},
)
.style(move |s| {
s.items_center()
.padding_horiz(10.0)
.min_width(200.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
config.get().color(LapceColor::LAPCE_BORDER),
)
})
},
{
let keymap = keymap.clone();
let bits = [
(Modes::INSERT, "Insert"),
(Modes::NORMAL, "Normal"),
(Modes::VISUAL, "Visual"),
(Modes::TERMINAL, "Terminal"),
];
let modes = keymap
.as_ref()
.map(|keymap| {
bits.iter()
.filter_map(|(bit, mode)| {
if keymap.modes.contains(*bit) {
Some(mode.to_string())
} else {
None
}
})
.collect::<Vec<String>>()
})
.unwrap_or_default();
dyn_stack(
move || modes.clone(),
|m| m.clone(),
move |mode| {
text(mode.clone()).style(move |s| {
s.padding_horiz(5.0)
.padding_vert(1.0)
.margin_right(5.0)
.border(1.0)
.border_radius(3.0)
.border_color(
config.get().color(LapceColor::LAPCE_BORDER),
)
})
},
)
.style(move |s| {
s.items_center()
.padding_horiz(10.0)
.min_width(200.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
config.get().color(LapceColor::LAPCE_BORDER),
)
.apply_if(!modal.get(), |s| s.hide())
})
},
container(
text(
keymap
.as_ref()
.and_then(|keymap| keymap.when.clone())
.unwrap_or_default(),
)
.style(|s| {
s.text_ellipsis()
.absolute()
.items_center()
.min_width(0.0)
.padding_horiz(10.0)
.size_pct(100.0, 100.0)
}),
)
.style(move |s| {
s.height_pct(100.0)
.min_width(0.0)
.flex_basis(0.0)
.flex_grow(1.0)
}),
))
.on_click_stop(move |_| {
let keymap = if let Some(keymap) = local_keymap.clone() {
keymap
} else {
@@ -267,157 +265,111 @@ pub fn keymap_view(common: CommonData) -> impl View {
picker.keys.update(|keys| {
keys.clear();
});
true
})
.style(move || {
.style(move |s| {
let config = config.get();
Style::BASE
.items_center()
.height_px(ui_line_height() as f32)
s.items_center()
.height(ui_line_height() as f32)
.width_pct(100.0)
.apply_if(i % 2 > 0, |s| {
s.background(
*config.get_color(LapceColor::EDITOR_CURRENT_LINE),
)
s.background(config.color(LapceColor::EDITOR_CURRENT_LINE))
})
.border_bottom(1.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.border_color(config.color(LapceColor::LAPCE_BORDER))
})
};
stack(|| {
(
{
container(|| {
text_input(editor, || false)
.placeholder(|| "Search Key Bindings".to_string())
.keyboard_navigatable()
.style(move || {
Style::BASE
.width_pct(100.0)
.border_radius(6.0)
.border(1.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
})
.style(|| Style::BASE.padding_bottom_px(10.0).width_pct(100.0))
},
stack(move || {
(
container(|| {
label(|| "Command".to_string()).style(move || {
Style::BASE
.text_ellipsis()
.padding_horiz_px(10.0)
.min_width_px(0.0)
})
})
.style(move || {
Style::BASE
.items_center()
.height_pct(100.0)
.min_width_px(0.0)
.flex_basis_px(0.0)
.flex_grow(1.0)
.border_right(1.0)
.border_color(
*config.get().get_color(LapceColor::LAPCE_BORDER),
)
}),
label(|| "Key Binding".to_string()).style(move || {
Style::BASE
.width_px(200.0)
.items_center()
.padding_horiz_px(10.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
*config.get().get_color(LapceColor::LAPCE_BORDER),
)
}),
label(|| "Modes".to_string()).style(move || {
Style::BASE
.width_px(200.0)
.items_center()
.padding_horiz_px(10.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(
*config.get().get_color(LapceColor::LAPCE_BORDER),
)
.apply_if(!modal.get(), |s| s.hide())
}),
container(|| {
label(|| "When".to_string()).style(move || {
Style::BASE
.text_ellipsis()
.padding_horiz_px(10.0)
.min_width_px(0.0)
})
})
.style(move || {
Style::BASE
.items_center()
.height_pct(100.0)
.min_width_px(0.0)
.flex_basis_px(0.0)
.flex_grow(1.0)
}),
)
})
.style(move || {
let config = config.get();
Style::BASE
.font_bold()
.height_px(ui_line_height() as f32)
.width_pct(100.0)
.border_top(1.0)
.border_bottom(1.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.background(*config.get_color(LapceColor::EDITOR_CURRENT_LINE))
stack((
container(
text_input_view
.placeholder(|| "Search Key Bindings".to_string())
.keyboard_navigatable()
.request_focus(|| {})
.style(move |s| {
s.width_pct(100.0)
.border_radius(6.0)
.border(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
}),
)
.style(|s| s.padding_bottom(10.0).width_pct(100.0)),
stack((
container(text("Command").style(move |s| {
s.text_ellipsis().padding_horiz(10.0).min_width(0.0)
}))
.style(move |s| {
s.items_center()
.height_pct(100.0)
.min_width(0.0)
.flex_basis(0.0)
.flex_grow(1.0)
.border_right(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
}),
container(|| {
scroll(|| {
virtual_list(
VirtualListDirection::Vertical,
VirtualListItemSize::Fixed(Box::new(ui_line_height)),
items,
|(i, (cmd, keymap)): &(
usize,
(LapceCommand, Option<KeyMap>),
)| {
(*i, cmd.kind.str(), keymap.clone())
},
view_fn,
)
.style(|| Style::BASE.flex_col().width_pct(100.0))
})
.scroll_bar_color(move || {
*config.get().get_color(LapceColor::LAPCE_SCROLL_BAR)
})
.style(|| Style::BASE.absolute().size_pct(100.0, 100.0))
})
.style(|| {
Style::BASE
.width_pct(100.0)
.flex_basis_px(0.0)
text("Key Binding").style(move |s| {
s.width(200.0)
.items_center()
.padding_horiz(10.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
}),
text("Modes").style(move |s| {
s.width(200.0)
.items_center()
.padding_horiz(10.0)
.height_pct(100.0)
.border_right(1.0)
.border_color(config.get().color(LapceColor::LAPCE_BORDER))
.apply_if(!modal.get(), |s| s.hide())
}),
container(text("When").style(move |s| {
s.text_ellipsis().padding_horiz(10.0).min_width(0.0)
}))
.style(move |s| {
s.items_center()
.height_pct(100.0)
.min_width(0.0)
.flex_basis(0.0)
.flex_grow(1.0)
}),
keyboard_picker_view(picker, common.ui_line_height, config),
))
.style(move |s| {
let config = config.get();
s.font_bold()
.height(ui_line_height() as f32)
.width_pct(100.0)
.border_top(1.0)
.border_bottom(1.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
.background(config.color(LapceColor::EDITOR_CURRENT_LINE))
}),
container(
scroll(
virtual_stack(
VirtualDirection::Vertical,
VirtualItemSize::Fixed(Box::new(ui_line_height)),
items,
|(i, (cmd, keymap)): &(
usize,
(LapceCommand, Option<KeyMap>),
)| { (*i, cmd.kind.str(), keymap.clone()) },
view_fn,
)
.style(|s| s.flex_col().width_pct(100.0)),
)
.style(|s| s.absolute().size_pct(100.0, 100.0)),
)
})
.style(|| {
Style::BASE
.absolute()
.style(|s| s.width_pct(100.0).flex_basis(0.0).flex_grow(1.0)),
keyboard_picker_view(picker, common.ui_line_height, config),
))
.style(|s| {
s.absolute()
.size_pct(100.0, 100.0)
.flex_col()
.padding_top_px(20.0)
.padding_left_px(20.0)
.padding_right_px(20.0)
.padding_top(20.0)
.padding_left(20.0)
.padding_right(20.0)
})
}
@@ -427,175 +379,170 @@ fn keyboard_picker_view(
config: ReadSignal<Arc<LapceConfig>>,
) -> impl View {
let picker_cmd = picker.cmd;
let view = container(|| {
stack(|| {
(
label(move || {
picker_cmd.with(|cmd| {
cmd.as_ref()
.map(|cmd| {
cmd.kind
.desc()
.map(|desc| desc.to_string())
.unwrap_or_else(|| {
cmd.kind.str().replace('_', " ")
})
})
.unwrap_or_default()
})
}),
list(
move || {
picker
.keys
.get()
.iter()
.map(|key| key.label().trim().to_string())
.filter(|l| !l.is_empty())
.enumerate()
.collect::<Vec<(usize, String)>>()
},
|(i, k)| (*i, k.clone()),
move |(_, key)| {
label(move || key.clone()).style(move || {
Style::BASE
.padding_horiz_px(5.0)
.padding_vert_px(1.0)
.margin_right_px(5.0)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
let view = container(
stack((
label(move || {
picker_cmd.with(|cmd| {
cmd.as_ref()
.map(|cmd| {
cmd.kind
.desc()
.map(|desc| desc.to_string())
.unwrap_or_else(|| cmd.kind.str().replace('_', " "))
})
},
)
.style(move || {
let config = config.get();
Style::BASE
.items_center()
.justify_center()
.width_pct(100.0)
.margin_top_px(20.0)
.height_px(ui_line_height.get() as f32 + 16.0)
.border(1.0)
.border_radius(6.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.background(*config.get_color(LapceColor::EDITOR_BACKGROUND))
}),
stack(|| {
(
label(|| "Save".to_string())
.style(move || {
Style::BASE
.width_px(100.0)
.justify_center()
.padding_vert_px(8.0)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
.on_click(move |_| {
let keymap = picker.keymap.get_untracked();
if let Some(keymap) = keymap {
let keys = picker.keys.get_untracked();
picker.keymap.set(None);
KeyPressData::update_file(&keymap, &keys);
}
true
})
.hover_style(move || {
Style::BASE.cursor(CursorStyle::Pointer).background(
*config.get().get_color(
LapceColor::PANEL_HOVERED_BACKGROUND,
),
)
})
.active_style(move || {
Style::BASE.background(*config.get().get_color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
}),
label(|| "Cancel".to_string())
.style(move || {
Style::BASE
.margin_left_px(20.0)
.width_px(100.0)
.justify_center()
.padding_vert_px(8.0)
.border(1.0)
.border_radius(6.0)
.border_color(
*config
.get()
.get_color(LapceColor::LAPCE_BORDER),
)
})
.on_click(move |_| {
picker.keymap.set(None);
true
})
.hover_style(move || {
Style::BASE.cursor(CursorStyle::Pointer).background(
*config.get().get_color(
LapceColor::PANEL_HOVERED_BACKGROUND,
),
)
})
.active_style(move || {
Style::BASE.background(*config.get().get_color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
}),
)
.unwrap_or_default()
})
.style(move || {
let config = config.get();
Style::BASE
.items_center()
.justify_center()
.width_pct(100.0)
.margin_top_px(20.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
}),
}),
dyn_stack(
move || {
picker
.keys
.get()
.iter()
.map(|(key, _)| key.label())
.filter(|l| !l.is_empty())
.enumerate()
.collect::<Vec<(usize, String)>>()
},
|(i, k)| (*i, k.clone()),
move |(_, key)| {
text(key.clone()).style(move |s| {
s.padding_horiz(5.0)
.padding_vert(1.0)
.margin_right(5.0)
.border(1.0)
.border_radius(6.0)
.border_color(
config.get().color(LapceColor::LAPCE_BORDER),
)
})
},
)
})
.style(move || {
.style(move |s| {
let config = config.get();
s.items_center()
.justify_center()
.width_pct(100.0)
.margin_top(20.0)
.height(ui_line_height.get() as f32 + 16.0)
.border(1.0)
.border_radius(6.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
.background(config.color(LapceColor::EDITOR_BACKGROUND))
}),
stack((
text("Save")
.style(move |s| {
let config = config.get();
s.width(100.0)
.justify_center()
.padding_vert(8.0)
.border(1.0)
.border_radius(6.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
.hover(|s| {
s.cursor(CursorStyle::Pointer).background(
config
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
)
})
.active(|s| {
s.background(config.color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
})
})
.on_click_stop(move |_| {
let keymap = picker.keymap.get_untracked();
if let Some(keymap) = keymap {
let keys = picker.keys.get_untracked();
picker.keymap.set(None);
KeyPressData::update_file(
&keymap,
&keys
.iter()
.map(|(key, _)| key.clone())
.collect::<Vec<KeyMapPress>>(),
);
}
}),
text("Cancel")
.style(move |s| {
let config = config.get();
s.margin_left(20.0)
.width(100.0)
.justify_center()
.padding_vert(8.0)
.border(1.0)
.border_radius(6.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
.hover(|s| {
s.cursor(CursorStyle::Pointer).background(
config
.color(LapceColor::PANEL_HOVERED_BACKGROUND),
)
})
.active(|s| {
s.background(config.color(
LapceColor::PANEL_HOVERED_ACTIVE_BACKGROUND,
))
})
})
.on_click_stop(move |_| {
picker.keymap.set(None);
}),
))
.style(move |s| {
let config = config.get();
s.items_center()
.justify_center()
.width_pct(100.0)
.margin_top(20.0)
.border_color(config.color(LapceColor::LAPCE_BORDER))
}),
))
.style(move |s| {
let config = config.get();
Style::BASE
.items_center()
s.items_center()
.flex_col()
.padding_px(20.0)
.width_px(400.0)
.padding(20.0)
.width(400.0)
.border(1.0)
.border_radius(6.0)
.border_color(*config.get_color(LapceColor::LAPCE_BORDER))
.background(*config.get_color(LapceColor::PANEL_BACKGROUND))
})
})
.border_color(config.color(LapceColor::LAPCE_BORDER))
.background(config.color(LapceColor::PANEL_BACKGROUND))
}),
)
.keyboard_navigatable()
.on_event(EventListener::KeyDown, move |event| {
.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 keys.len() == 2 {
keys.clear();
}
keys.push(keypress);
keys.push((keypress, false));
})
}
}
true
})
.style(move || {
Style::BASE
.absolute()
.on_event_stop(EventListener::KeyUp, move |event| {
if let Event::KeyUp(_key_event) = event {
picker.keys.update(|keys| {
if let Some((_last_key, last_key_confirmed)) = keys.last_mut() {
*last_key_confirmed = true;
}
})
}
})
.style(move |s| {
s.absolute()
.size_pct(100.0, 100.0)
.items_center()
.justify_center()
+136 -119
View File
@@ -8,30 +8,29 @@ use std::{path::PathBuf, rc::Rc, str::FromStr};
use anyhow::Result;
use floem::{
glazier::{KbKey, KeyEvent, Modifiers, PointerEvent},
keyboard::{Key, KeyEvent, KeyEventExtModifierSupplement, Modifiers, NamedKey},
pointer::PointerInputEvent,
reactive::{RwSignal, Scope},
};
use indexmap::IndexMap;
use itertools::Itertools;
use lapce_core::mode::{Mode, Modes};
use tracing::{debug, error};
use self::{key::Key, keymap::KeyMap, loader::KeyMapLoader};
pub use self::press::KeyPress;
use self::{
key::KeyInput,
keymap::{KeyMap, KeyMapPress},
loader::KeyMapLoader,
};
use crate::{
command::{
lapce_internal_commands, CommandExecuted, CommandKind, LapceCommand,
LapceWorkbenchCommand,
},
command::{lapce_internal_commands, CommandExecuted, CommandKind, LapceCommand},
config::LapceConfig,
keypress::{
condition::{CheckCondition, Condition},
keymap::KeymapMatch,
},
listener::Listener,
};
pub use self::press::KeyPress;
const DEFAULT_KEYMAPS_COMMON: &str =
include_str!("../../defaults/keymaps-common.toml");
const DEFAULT_KEYMAPS_MACOS: &str =
@@ -61,11 +60,69 @@ pub trait KeyPressFocus {
fn receive_char(&self, c: &str);
}
impl KeyPressFocus for () {
fn get_mode(&self) -> Mode {
Mode::Normal
}
fn check_condition(&self, _condition: Condition) -> bool {
false
}
fn run_command(
&self,
_command: &LapceCommand,
_count: Option<usize>,
_mods: Modifiers,
) -> CommandExecuted {
CommandExecuted::No
}
fn expect_char(&self) -> bool {
false
}
fn focus_only(&self) -> bool {
false
}
fn receive_char(&self, _c: &str) {}
}
impl KeyPressFocus for Box<dyn KeyPressFocus> {
fn get_mode(&self) -> Mode {
(**self).get_mode()
}
fn check_condition(&self, condition: Condition) -> bool {
(**self).check_condition(condition)
}
fn run_command(
&self,
command: &LapceCommand,
count: Option<usize>,
mods: Modifiers,
) -> CommandExecuted {
(**self).run_command(command, count, mods)
}
fn expect_char(&self) -> bool {
(**self).expect_char()
}
fn focus_only(&self) -> bool {
(**self).focus_only()
}
fn receive_char(&self, c: &str) {
(**self).receive_char(c)
}
}
#[derive(Clone, Copy, Debug)]
pub enum EventRef<'a> {
Keyboard(&'a floem::glazier::KeyEvent),
Pointer(&'a floem::glazier::PointerEvent),
Keyboard(&'a floem::keyboard::KeyEvent),
Pointer(&'a floem::pointer::PointerInputEvent),
}
impl<'a> From<&'a KeyEvent> for EventRef<'a> {
@@ -74,8 +131,8 @@ impl<'a> From<&'a KeyEvent> for EventRef<'a> {
}
}
impl<'a> From<&'a PointerEvent> for EventRef<'a> {
fn from(ev: &'a PointerEvent) -> Self {
impl<'a> From<&'a PointerInputEvent> for EventRef<'a> {
fn from(ev: &'a PointerInputEvent) -> Self {
Self::Pointer(ev)
}
}
@@ -84,20 +141,15 @@ impl<'a> From<&'a PointerEvent> for EventRef<'a> {
pub struct KeyPressData {
count: RwSignal<Option<usize>>,
pending_keypress: RwSignal<Vec<KeyPress>>,
workbench_cmd: Listener<LapceWorkbenchCommand>,
pub commands: Rc<IndexMap<String, LapceCommand>>,
pub keymaps: Rc<IndexMap<Vec<KeyPress>, Vec<KeyMap>>>,
pub keymaps: Rc<IndexMap<Vec<KeyMapPress>, Vec<KeyMap>>>,
pub command_keymaps: Rc<IndexMap<String, Vec<KeyMap>>>,
pub commands_with_keymap: Rc<Vec<KeyMap>>,
pub commands_without_keymap: Rc<Vec<LapceCommand>>,
}
impl KeyPressData {
pub fn new(
cx: Scope,
config: &LapceConfig,
workbench_cmd: Listener<LapceWorkbenchCommand>,
) -> Self {
pub fn new(cx: Scope, config: &LapceConfig) -> Self {
let (keymaps, command_keymaps) =
Self::get_keymaps(config).unwrap_or((IndexMap::new(), IndexMap::new()));
let mut keypress = Self {
@@ -108,7 +160,6 @@ impl KeyPressData {
commands: Rc::new(lapce_internal_commands()),
commands_with_keymap: Rc::new(Vec::new()),
commands_without_keymap: Rc::new(Vec::new()),
workbench_cmd,
};
keypress.load_commands();
keypress
@@ -143,7 +194,7 @@ impl KeyPressData {
self.commands_without_keymap = Rc::new(commands_without_keymap);
}
fn handle_count<T: KeyPressFocus>(
fn handle_count<T: KeyPressFocus + ?Sized>(
&self,
focus: &T,
keypress: &KeyPress,
@@ -160,7 +211,11 @@ impl KeyPressData {
return false;
}
if let Key::Keyboard(KbKey::Character(c)) = &keypress.key {
if let KeyInput::Keyboard {
logical: Key::Character(c),
..
} = &keypress.key
{
if let Ok(n) = c.parse::<usize>() {
if self.count.with_untracked(|count| count.is_some()) || n > 0 {
self.count
@@ -173,7 +228,7 @@ impl KeyPressData {
false
}
fn run_command<T: KeyPressFocus>(
fn run_command<T: KeyPressFocus + ?Sized>(
&self,
command: &str,
count: Option<usize>,
@@ -181,19 +236,7 @@ impl KeyPressData {
focus: &T,
) -> CommandExecuted {
if let Some(cmd) = self.commands.get(command) {
match &cmd.kind {
CommandKind::Workbench(cmd) => {
self.workbench_cmd.send(cmd.clone());
CommandExecuted::Yes
}
CommandKind::Move(_)
| CommandKind::Edit(_)
| CommandKind::Focus(_)
| CommandKind::MotionMode(_)
| CommandKind::MultiSelection(_) => {
focus.run_command(cmd, count, mods)
}
}
focus.run_command(cmd, count, mods)
} else {
CommandExecuted::No
}
@@ -201,28 +244,26 @@ impl KeyPressData {
pub fn keypress<'a>(event: impl Into<EventRef<'a>>) -> Option<KeyPress> {
let event = event.into();
debug!("{event:?}");
tracing::trace!("{event:?}");
let keypress = match event {
EventRef::Keyboard(ev)
if ev.key == KbKey::Shift && ev.mods.is_empty() =>
{
return None;
}
EventRef::Keyboard(ev) => KeyPress {
key: Key::Keyboard(ev.key.clone()),
// We are removing Shift modifier since the character is already upper case.
key: KeyInput::Keyboard {
logical: ev.key.logical_key.to_owned(),
physical: ev.key.physical_key,
key_without_modifiers: ev.key.key_without_modifiers(),
},
mods: Self::get_key_modifiers(ev),
},
EventRef::Pointer(ev) => KeyPress {
key: Key::Pointer(ev.button),
key: KeyInput::Pointer(ev.button),
mods: ev.modifiers,
},
};
Some(keypress)
}
pub fn key_down<'a, T: KeyPressFocus>(
pub fn key_down<'a, T: KeyPressFocus + ?Sized>(
&self,
event: impl Into<EventRef<'a>>,
focus: &T,
@@ -233,9 +274,8 @@ impl KeyPressData {
};
let mods = keypress.mods;
let mode = focus.get_mode();
if self.handle_count(focus, &keypress) {
return false;
return true;
}
self.pending_keypress.update(|pending_keypress| {
@@ -251,8 +291,8 @@ impl KeyPressData {
pending_keypress.clear();
});
let count = self.count.try_update(|count| count.take()).unwrap();
self.run_command(&command, count, mods, focus);
return true;
return self.run_command(&command, count, mods, focus)
== CommandExecuted::Yes;
}
KeymapMatch::Multiple(commands) => {
self.pending_keypress.update(|pending_keypress| {
@@ -267,12 +307,12 @@ impl KeyPressData {
}
}
return true;
return false;
}
KeymapMatch::Prefix => {
// Here pending_keypress contains only a prefix of some keymap, so let's keep
// collecting key presses.
return false;
return true;
}
KeymapMatch::None => {
self.pending_keypress.update(|pending_keypress| {
@@ -286,8 +326,8 @@ impl KeyPressData {
{
if let Some(cmd) = self.commands.get(&command) {
if let CommandKind::Move(_) = cmd.kind {
focus.run_command(cmd, None, mods);
return true;
return focus.run_command(cmd, None, mods)
== CommandExecuted::Yes;
}
}
}
@@ -295,35 +335,27 @@ impl KeyPressData {
}
}
if mode != Mode::Insert
&& mode != Mode::Terminal
&& self.handle_count(focus, &keypress)
{
return false;
}
self.count.set(None);
let mut mods = keypress.mods;
#[cfg(not(target_os = "macos"))]
{
mods.set(Modifiers::SHIFT, false);
if mods.is_empty() {
if let Key::Keyboard(KbKey::Character(c)) = &keypress.key {
focus.receive_char(c);
return true;
}
}
}
#[cfg(target_os = "macos")]
{
mods.set(Modifiers::SHIFT, false);
mods.set(Modifiers::ALT, false);
if mods.is_empty() {
if let Key::Keyboard(KbKey::Character(c)) = &keypress.key {
}
#[cfg(not(target_os = "macos"))]
{
mods.set(Modifiers::SHIFT, false);
mods.set(Modifiers::ALTGR, false);
}
if mods.is_empty() {
if let KeyInput::Keyboard { logical, .. } = &keypress.key {
if let Key::Character(c) = logical {
focus.receive_char(c);
self.count.set(None);
return true;
} else if let Key::Named(NamedKey::Space) = logical {
focus.receive_char(" ");
self.count.set(None);
return true;
}
}
@@ -333,42 +365,28 @@ impl KeyPressData {
}
fn get_key_modifiers(key_event: &KeyEvent) -> Modifiers {
// We only care about some modifiers
let mut mods = (Modifiers::ALT
| Modifiers::CONTROL
| Modifiers::SHIFT
| Modifiers::META)
& key_event.mods;
let mut mods = key_event.modifiers;
if mods == Modifiers::SHIFT {
if let KbKey::Character(c) = &key_event.key {
if !c.chars().all(|c| c.is_alphabetic()) {
// We remove the shift if there's only shift pressed,
// and the character isn't a letter
return Modifiers::empty();
}
}
}
match &key_event.key {
KbKey::Shift => mods.set(Modifiers::SHIFT, false),
KbKey::Alt => mods.set(Modifiers::ALT, false),
KbKey::Meta => mods.set(Modifiers::META, false),
KbKey::Control => mods.set(Modifiers::CONTROL, false),
match &key_event.key.logical_key {
Key::Named(NamedKey::Shift) => mods.set(Modifiers::SHIFT, false),
Key::Named(NamedKey::Alt) => mods.set(Modifiers::ALT, false),
Key::Named(NamedKey::Meta) => mods.set(Modifiers::META, false),
Key::Named(NamedKey::Control) => mods.set(Modifiers::CONTROL, false),
Key::Named(NamedKey::AltGraph) => mods.set(Modifiers::ALTGR, false),
_ => (),
}
mods
}
fn match_keymap<T: KeyPressFocus>(
fn match_keymap<T: KeyPressFocus + ?Sized>(
&self,
keypresses: &[KeyPress],
check: &T,
) -> KeymapMatch {
let keypresses: Vec<KeyPress> =
keypresses.iter().map(KeyPress::to_lowercase).collect();
let matches = self
let keypresses: Vec<KeyMapPress> =
keypresses.iter().map(|k| k.keymap_press()).collect();
let matches: Vec<_> = self
.keymaps
.get(&keypresses)
.map(|keymaps| {
@@ -395,7 +413,7 @@ impl KeyPressData {
})
.collect()
})
.unwrap_or_else(Vec::new);
.unwrap_or_default();
if matches.is_empty() {
KeymapMatch::None
@@ -412,8 +430,11 @@ impl KeyPressData {
}
}
fn check_condition<T: KeyPressFocus>(condition: &str, check: &T) -> bool {
fn check_one_condition<T: KeyPressFocus>(
fn check_condition<T: KeyPressFocus + ?Sized>(
condition: &str,
check: &T,
) -> bool {
fn check_one_condition<T: KeyPressFocus + ?Sized>(
condition: &str,
check: &T,
) -> bool {
@@ -454,7 +475,7 @@ impl KeyPressData {
fn get_keymaps(
config: &LapceConfig,
) -> Result<(
IndexMap<Vec<KeyPress>, Vec<KeyMap>>,
IndexMap<Vec<KeyMapPress>, Vec<KeyMap>>,
IndexMap<String, Vec<KeyMap>>,
)> {
let is_modal = config.core.modal;
@@ -462,7 +483,7 @@ impl KeyPressData {
let mut loader = KeyMapLoader::new();
if let Err(err) = loader.load_from_str(DEFAULT_KEYMAPS_COMMON, is_modal) {
error!("Failed to load common defaults: {err}");
tracing::error!("Failed to load common defaults: {err}");
}
let os_keymaps = if std::env::consts::OS == "macos" {
@@ -472,13 +493,13 @@ impl KeyPressData {
};
if let Err(err) = loader.load_from_str(os_keymaps, is_modal) {
error!("Failed to load OS defaults: {err}");
tracing::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) {
error!("Failed to load from {path:?}: {err}");
tracing::warn!("Failed to load from {path:?}: {err}");
}
}
}
@@ -501,7 +522,7 @@ impl KeyPressData {
.cloned()
}
pub fn update_file(keymap: &KeyMap, keys: &[KeyPress]) -> Option<()> {
pub fn update_file(keymap: &KeyMap, keys: &[KeyMapPress]) -> Option<()> {
let mut array = Self::get_file_array().unwrap_or_default();
let index = array.iter().position(|value| {
Some(keymap.command.as_str())
@@ -513,16 +534,14 @@ impl KeyPressData {
== value
.get("key")
.and_then(|v| v.as_str())
.map(KeyPress::parse)
.map(KeyMapPress::parse)
});
if let Some(index) = index {
if !keys.is_empty() {
array.get_mut(index)?.insert(
"key",
toml_edit::value(toml_edit::Value::from(
keys.iter().map(|k| k.to_string()).join(" "),
)),
toml_edit::value(toml_edit::Value::from(keys.iter().join(" "))),
);
} else {
array.remove(index);
@@ -551,9 +570,7 @@ impl KeyPressData {
if !keys.is_empty() {
table.insert(
"key",
toml_edit::value(toml_edit::Value::from(
keys.iter().map(|k| k.to_string()).join(" "),
)),
toml_edit::value(toml_edit::Value::from(keys.iter().join(" "))),
);
array.push(table.clone());
}
@@ -562,7 +579,7 @@ impl KeyPressData {
table.insert(
"key",
toml_edit::value(toml_edit::Value::from(
keymap.key.iter().map(|k| k.to_string()).join(" "),
keymap.key.iter().join(" "),
)),
);
table.insert(
+3 -1
View File
@@ -49,6 +49,8 @@ pub enum Condition {
PaletteFocus,
#[strum(serialize = "completion_focus")]
CompletionFocus,
#[strum(serialize = "inline_completion_visible")]
InlineCompletionVisible,
#[strum(serialize = "modal_focus")]
ModalFocus,
#[strum(serialize = "in_snippet")]
@@ -71,7 +73,7 @@ pub enum Condition {
#[cfg(test)]
mod test {
use floem::glazier::Modifiers;
use floem::keyboard::Modifiers;
use lapce_core::mode::Mode;
use super::Condition;
+32 -380
View File
@@ -1,389 +1,41 @@
use std::{
fmt::Display,
hash::{Hash, Hasher},
str::FromStr,
};
use floem::keyboard::{Key, NamedKey, PhysicalKey};
use floem::glazier::KbKey;
use super::keymap::KeyMapKey;
#[derive(Clone, Debug, Eq)]
pub(crate) enum Key {
Keyboard(floem::glazier::KbKey),
Pointer(floem::glazier::PointerButton),
#[derive(Clone, Debug)]
pub(crate) enum KeyInput {
Keyboard {
physical: PhysicalKey,
logical: Key,
key_without_modifiers: Key,
},
Pointer(floem::pointer::PointerButton),
}
impl Key {
fn keyboard_from_str(s: &str) -> Option<KbKey> {
// Checks if it is a character key
fn is_key_string(s: &str) -> bool {
s.chars().all(|c| !c.is_control())
&& s.chars().skip(1).all(|c| !c.is_ascii())
}
// Import into scope to reduce noise
use floem::glazier::keyboard_types::Key::*;
Some(match s {
s if is_key_string(s) => Character(s.to_string()),
"unidentified" => Unidentified,
"alt" => Alt,
"altgraph" => AltGraph,
"capslock" => CapsLock,
"control" => Control,
"fn" => Fn,
"fnlock" => FnLock,
"meta" => Meta,
"numlock" => NumLock,
"scrolllock" => ScrollLock,
"shift" => Shift,
"symbol" => Symbol,
"symbollock" => SymbolLock,
"hyper" => Hyper,
"super" => Super,
"enter" => Enter,
"tab" => Tab,
"arrowdown" => ArrowDown,
"arrowleft" => ArrowLeft,
"arrowright" => ArrowRight,
"arrowup" => ArrowUp,
"end" => End,
"home" => Home,
"pagedown" => PageDown,
"pageup" => PageUp,
"backspace" => Backspace,
"clear" => Clear,
"copy" => Copy,
"crsel" => CrSel,
"cut" => Cut,
"delete" => Delete,
"eraseeof" => EraseEof,
"exsel" => ExSel,
"insert" => Insert,
"paste" => Paste,
"redo" => Redo,
"undo" => Undo,
"accept" => Accept,
"again" => Again,
"attn" => Attn,
"cancel" => Cancel,
"contextmenu" => ContextMenu,
"escape" => Escape,
"execute" => Execute,
"find" => Find,
"help" => Help,
"pause" => Pause,
"play" => Play,
"props" => Props,
"select" => Select,
"zoomin" => ZoomIn,
"zoomout" => ZoomOut,
"brightnessdown" => BrightnessDown,
"brightnessup" => BrightnessUp,
"eject" => Eject,
"logoff" => LogOff,
"power" => Power,
"poweroff" => PowerOff,
"printscreen" => PrintScreen,
"hibernate" => Hibernate,
"standby" => Standby,
"wakeup" => WakeUp,
"allcandidates" => AllCandidates,
"alphanumeric" => Alphanumeric,
"codeinput" => CodeInput,
"compose" => Compose,
"convert" => Convert,
"dead" => Dead,
"finalmode" => FinalMode,
"groupfirst" => GroupFirst,
"grouplast" => GroupLast,
"groupnext" => GroupNext,
"groupprevious" => GroupPrevious,
"modechange" => ModeChange,
"nextcandidate" => NextCandidate,
"nonconvert" => NonConvert,
"previouscandidate" => PreviousCandidate,
"process" => Process,
"singlecandidate" => SingleCandidate,
"hangulmode" => HangulMode,
"hanjamode" => HanjaMode,
"junjamode" => JunjaMode,
"eisu" => Eisu,
"hankaku" => Hankaku,
"hiragana" => Hiragana,
"hiraganakatakana" => HiraganaKatakana,
"kanamode" => KanaMode,
"kanjimode" => KanjiMode,
"katakana" => Katakana,
"romaji" => Romaji,
"zenkaku" => Zenkaku,
"zenkakuhankaku" => ZenkakuHankaku,
"f1" => F1,
"f2" => F2,
"f3" => F3,
"f4" => F4,
"f5" => F5,
"f6" => F6,
"f7" => F7,
"f8" => F8,
"f9" => F9,
"f10" => F10,
"f11" => F11,
"f12" => F12,
"soft1" => Soft1,
"soft2" => Soft2,
"soft3" => Soft3,
"soft4" => Soft4,
"channeldown" => ChannelDown,
"channelup" => ChannelUp,
"close" => Close,
"mailforward" => MailForward,
"mailreply" => MailReply,
"mailsend" => MailSend,
"mediaclose" => MediaClose,
"mediafastforward" => MediaFastForward,
"mediapause" => MediaPause,
"mediaplay" => MediaPlay,
"mediaplaypause" => MediaPlayPause,
"mediarecord" => MediaRecord,
"mediarewind" => MediaRewind,
"mediastop" => MediaStop,
"mediatracknext" => MediaTrackNext,
"mediatrackprevious" => MediaTrackPrevious,
"new" => New,
"open" => Open,
"print" => Print,
"save" => Save,
"spellcheck" => SpellCheck,
"key11" => Key11,
"key12" => Key12,
"audiobalanceleft" => AudioBalanceLeft,
"audiobalanceright" => AudioBalanceRight,
"audiobassboostdown" => AudioBassBoostDown,
"audiobassboosttoggle" => AudioBassBoostToggle,
"audiobassboostup" => AudioBassBoostUp,
"audiofaderfront" => AudioFaderFront,
"audiofaderrear" => AudioFaderRear,
"audiosurroundmodenext" => AudioSurroundModeNext,
"audiotrebledown" => AudioTrebleDown,
"audiotrebleup" => AudioTrebleUp,
"audiovolumedown" => AudioVolumeDown,
"audiovolumeup" => AudioVolumeUp,
"audiovolumemute" => AudioVolumeMute,
"microphonetoggle" => MicrophoneToggle,
"microphonevolumedown" => MicrophoneVolumeDown,
"microphonevolumeup" => MicrophoneVolumeUp,
"microphonevolumemute" => MicrophoneVolumeMute,
"speechcorrectionlist" => SpeechCorrectionList,
"speechinputtoggle" => SpeechInputToggle,
"launchapplication1" => LaunchApplication1,
"launchapplication2" => LaunchApplication2,
"launchcalendar" => LaunchCalendar,
"launchcontacts" => LaunchContacts,
"launchmail" => LaunchMail,
"launchmediaplayer" => LaunchMediaPlayer,
"launchmusicplayer" => LaunchMusicPlayer,
"launchphone" => LaunchPhone,
"launchscreensaver" => LaunchScreenSaver,
"launchspreadsheet" => LaunchSpreadsheet,
"launchwebbrowser" => LaunchWebBrowser,
"launchwebcam" => LaunchWebCam,
"launchwordprocessor" => LaunchWordProcessor,
"browserback" => BrowserBack,
"browserfavorites" => BrowserFavorites,
"browserforward" => BrowserForward,
"browserhome" => BrowserHome,
"browserrefresh" => BrowserRefresh,
"browsersearch" => BrowserSearch,
"browserstop" => BrowserStop,
"appswitch" => AppSwitch,
"call" => Call,
"camera" => Camera,
"camerafocus" => CameraFocus,
"endcall" => EndCall,
"goback" => GoBack,
"gohome" => GoHome,
"headsethook" => HeadsetHook,
"lastnumberredial" => LastNumberRedial,
"notification" => Notification,
"mannermode" => MannerMode,
"voicedial" => VoiceDial,
"tv" => TV,
"tv3dmode" => TV3DMode,
"tvantennacable" => TVAntennaCable,
"tvaudiodescription" => TVAudioDescription,
"tvaudiodescriptionmixdown" => TVAudioDescriptionMixDown,
"tvaudiodescriptionmixup" => TVAudioDescriptionMixUp,
"tvcontentsmenu" => TVContentsMenu,
"tvdataservice" => TVDataService,
"tvinput" => TVInput,
"tvinputcomponent1" => TVInputComponent1,
"tvinputcomponent2" => TVInputComponent2,
"tvinputcomposite1" => TVInputComposite1,
"tvinputcomposite2" => TVInputComposite2,
"tvinputhdmi1" => TVInputHDMI1,
"tvinputhdmi2" => TVInputHDMI2,
"tvinputhdmi3" => TVInputHDMI3,
"tvinputhdmi4" => TVInputHDMI4,
"tvinputvga1" => TVInputVGA1,
"tvmediacontext" => TVMediaContext,
"tvnetwork" => TVNetwork,
"tvnumberentry" => TVNumberEntry,
"tvpower" => TVPower,
"tvradioservice" => TVRadioService,
"tvsatellite" => TVSatellite,
"tvsatellitebs" => TVSatelliteBS,
"tvsatellitecs" => TVSatelliteCS,
"tvsatellitetoggle" => TVSatelliteToggle,
"tvterrestrialanalog" => TVTerrestrialAnalog,
"tvterrestrialdigital" => TVTerrestrialDigital,
"tvtimer" => TVTimer,
"avrinput" => AVRInput,
"avrpower" => AVRPower,
"colorf0red" => ColorF0Red,
"colorf1green" => ColorF1Green,
"colorf2yellow" => ColorF2Yellow,
"colorf3blue" => ColorF3Blue,
"colorf4grey" => ColorF4Grey,
"colorf5brown" => ColorF5Brown,
"closedcaptiontoggle" => ClosedCaptionToggle,
"dimmer" => Dimmer,
"displayswap" => DisplaySwap,
"dvr" => DVR,
"exit" => Exit,
"favoriteclear0" => FavoriteClear0,
"favoriteclear1" => FavoriteClear1,
"favoriteclear2" => FavoriteClear2,
"favoriteclear3" => FavoriteClear3,
"favoriterecall0" => FavoriteRecall0,
"favoriterecall1" => FavoriteRecall1,
"favoriterecall2" => FavoriteRecall2,
"favoriterecall3" => FavoriteRecall3,
"favoritestore0" => FavoriteStore0,
"favoritestore1" => FavoriteStore1,
"favoritestore2" => FavoriteStore2,
"favoritestore3" => FavoriteStore3,
"guide" => Guide,
"guidenextday" => GuideNextDay,
"guidepreviousday" => GuidePreviousDay,
"info" => Info,
"instantreplay" => InstantReplay,
"link" => Link,
"listprogram" => ListProgram,
"livecontent" => LiveContent,
"lock" => Lock,
"mediaapps" => MediaApps,
"mediaaudiotrack" => MediaAudioTrack,
"medialast" => MediaLast,
"mediaskipbackward" => MediaSkipBackward,
"mediaskipforward" => MediaSkipForward,
"mediastepbackward" => MediaStepBackward,
"mediastepforward" => MediaStepForward,
"mediatopmenu" => MediaTopMenu,
"navigatein" => NavigateIn,
"navigatenext" => NavigateNext,
"navigateout" => NavigateOut,
"navigateprevious" => NavigatePrevious,
"nextfavoritechannel" => NextFavoriteChannel,
"nextuserprofile" => NextUserProfile,
"ondemand" => OnDemand,
"pairing" => Pairing,
"pinpdown" => PinPDown,
"pinpmove" => PinPMove,
"pinptoggle" => PinPToggle,
"pinpup" => PinPUp,
"playspeeddown" => PlaySpeedDown,
"playspeedreset" => PlaySpeedReset,
"playspeedup" => PlaySpeedUp,
"randomtoggle" => RandomToggle,
"rclowbattery" => RcLowBattery,
"recordspeednext" => RecordSpeedNext,
"rfbypass" => RfBypass,
"scanchannelstoggle" => ScanChannelsToggle,
"screenmodenext" => ScreenModeNext,
"settings" => Settings,
"splitscreentoggle" => SplitScreenToggle,
"stbinput" => STBInput,
"stbpower" => STBPower,
"subtitle" => Subtitle,
"teletext" => Teletext,
"videomodenext" => VideoModeNext,
"wink" => Wink,
"zoomtoggle" => ZoomToggle,
// Custom key name mappings
"esc" => Escape,
"space" => Character(" ".to_string()),
"bs" => Backspace,
"up" => ArrowUp,
"down" => ArrowDown,
"right" => ArrowRight,
"left" => ArrowLeft,
"del" => Delete,
_ => return None,
})
}
fn mouse_from_str(s: &str) -> Option<floem::glazier::PointerButton> {
use floem::glazier::PointerButton as B;
Some(match s {
"mousemiddle" => B::Auxiliary,
"mouseforward" => B::X2,
"mousebackward" => B::X1,
_ => return None,
})
}
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use floem::glazier::PointerButton as B;
impl KeyInput {
pub fn keymap_key(&self) -> KeyMapKey {
match self {
Self::Keyboard(KbKey::Meta) => match std::env::consts::OS {
"macos" => "Cmd",
"windows" => "Win",
_ => "Meta",
KeyInput::Pointer(b) => KeyMapKey::Pointer(*b),
KeyInput::Keyboard {
physical,
key_without_modifiers,
..
} => 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),
},
Self::Keyboard(key) => return key.fmt(f),
Self::Pointer(B::Auxiliary) => "MouseMiddle",
Self::Pointer(B::X2) => "MouseForward",
Self::Pointer(B::X1) => "MouseBackward",
Self::Pointer(_) => "MouseUnimplemented",
}
.fmt(f)
}
}
impl FromStr for Key {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
Key::keyboard_from_str(&s)
.map(Key::Keyboard)
.or_else(|| Key::mouse_from_str(&s).map(Key::Pointer))
.ok_or(())
}
}
impl Hash for Key {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Keyboard(key) => key.hash(state),
// TODO: Implement `Hash` for `druid::MouseButton`
Self::Pointer(btn) => (*btn as u8).hash(state),
}
}
}
impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Key::Keyboard(a), Key::Keyboard(b)) => a.eq(b),
(Key::Pointer(a), Key::Pointer(b)) => a.eq(b),
_ => false,
}
}
}
+679 -3
View File
@@ -1,6 +1,10 @@
use lapce_core::mode::Modes;
use std::{fmt::Display, str::FromStr};
use super::KeyPress;
use floem::{
keyboard::{Key, KeyCode, Modifiers, NamedKey, PhysicalKey},
pointer::PointerButton,
};
use lapce_core::mode::Modes;
#[derive(PartialEq, Debug)]
pub(super) enum KeymapMatch {
@@ -12,8 +16,680 @@ pub(super) enum KeymapMatch {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyMap {
pub key: Vec<KeyPress>,
pub key: Vec<KeyMapPress>,
pub modes: Modes,
pub when: Option<String>,
pub command: String,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum KeyMapKey {
Pointer(PointerButton),
Logical(Key),
Physical(PhysicalKey),
}
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::Logical(key) => key.hash(state),
Self::Physical(physical) => physical.hash(state),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct KeyMapPress {
pub key: KeyMapKey,
pub mods: Modifiers,
}
impl KeyMapPress {
pub fn is_char(&self) -> bool {
let mut mods = self.mods;
mods.set(Modifiers::SHIFT, false);
if mods.is_empty() {
if let KeyMapKey::Logical(Key::Character(_)) = &self.key {
return true;
}
}
false
}
pub fn is_modifiers(&self) -> bool {
if let KeyMapKey::Physical(physical) = &self.key {
matches!(
physical,
PhysicalKey::Code(KeyCode::Meta)
| PhysicalKey::Code(KeyCode::SuperLeft)
| PhysicalKey::Code(KeyCode::SuperRight)
| PhysicalKey::Code(KeyCode::ShiftLeft)
| PhysicalKey::Code(KeyCode::ShiftRight)
| PhysicalKey::Code(KeyCode::ControlLeft)
| PhysicalKey::Code(KeyCode::ControlRight)
| PhysicalKey::Code(KeyCode::AltLeft)
| PhysicalKey::Code(KeyCode::AltRight)
)
} else if let KeyMapKey::Logical(Key::Named(key)) = &self.key {
matches!(
key,
NamedKey::Meta
| NamedKey::Super
| NamedKey::Shift
| NamedKey::Control
| NamedKey::Alt
| NamedKey::AltGraph
)
} else {
false
}
}
pub fn label(&self) -> String {
let mut keys = String::from("");
if self.mods.control() {
keys.push_str("Ctrl+");
}
if self.mods.alt() {
keys.push_str("Alt+");
}
if self.mods.altgr() {
keys.push_str("AltGr+");
}
if self.mods.meta() {
let keyname = match std::env::consts::OS {
"macos" => "Cmd+",
"windows" => "Win+",
_ => "Meta+",
};
keys.push_str(keyname);
}
if self.mods.shift() {
keys.push_str("Shift+");
}
keys.push_str(&self.key.to_string());
keys
}
pub fn parse(key: &str) -> Vec<Self> {
key.split(' ')
.filter_map(|k| {
let (modifiers, key) = if k == "+" {
("", "+")
} else if let Some(remaining) = k.strip_suffix("++") {
(remaining, "+")
} else {
match k.rsplit_once('+') {
Some(pair) => pair,
None => ("", k),
}
};
let key = match key.parse().ok() {
Some(key) => key,
None => {
// Skip past unrecognized key definitions
tracing::warn!("Unrecognized key: {key}");
return None;
}
};
let mut mods = Modifiers::empty();
for part in modifiers.to_lowercase().split('+') {
match part {
"ctrl" => mods.set(Modifiers::CONTROL, true),
"meta" => mods.set(Modifiers::META, true),
"shift" => mods.set(Modifiers::SHIFT, true),
"alt" => mods.set(Modifiers::ALT, true),
"altgr" => mods.set(Modifiers::ALTGR, true),
"" => (),
other => tracing::warn!("Invalid key modifier: {}", other),
}
}
Some(KeyMapPress { key, mods })
})
.collect()
}
}
impl FromStr for KeyMapKey {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let key = if s.starts_with('[') && s.ends_with(']') {
let code = match s[1..s.len() - 2].to_lowercase().as_str() {
"esc" => KeyCode::Escape,
"space" => KeyCode::Space,
"bs" => KeyCode::Backspace,
"up" => KeyCode::ArrowUp,
"down" => KeyCode::ArrowDown,
"left" => KeyCode::ArrowLeft,
"right" => KeyCode::ArrowRight,
"del" => KeyCode::Delete,
"alt" => KeyCode::AltLeft,
"altgraph" => KeyCode::AltRight,
"capslock" => KeyCode::CapsLock,
"control" => KeyCode::ControlLeft,
"fn" => KeyCode::Fn,
"fnlock" => KeyCode::FnLock,
"meta" => KeyCode::Meta,
"numlock" => KeyCode::NumLock,
"scrolllock" => KeyCode::ScrollLock,
"shift" => KeyCode::ShiftLeft,
"hyper" => KeyCode::Hyper,
"super" => KeyCode::Meta,
"enter" => KeyCode::Enter,
"tab" => KeyCode::Tab,
"arrowdown" => KeyCode::ArrowDown,
"arrowleft" => KeyCode::ArrowLeft,
"arrowright" => KeyCode::ArrowRight,
"arrowup" => KeyCode::ArrowUp,
"end" => KeyCode::End,
"home" => KeyCode::Home,
"pagedown" => KeyCode::PageDown,
"pageup" => KeyCode::PageUp,
"backspace" => KeyCode::Backspace,
"copy" => KeyCode::Copy,
"cut" => KeyCode::Cut,
"delete" => KeyCode::Delete,
"insert" => KeyCode::Insert,
"paste" => KeyCode::Paste,
"undo" => KeyCode::Undo,
"again" => KeyCode::Again,
"contextmenu" => KeyCode::ContextMenu,
"escape" => KeyCode::Escape,
"find" => KeyCode::Find,
"help" => KeyCode::Help,
"pause" => KeyCode::Pause,
"play" => KeyCode::MediaPlayPause,
"props" => KeyCode::Props,
"select" => KeyCode::Select,
"eject" => KeyCode::Eject,
"power" => KeyCode::Power,
"printscreen" => KeyCode::PrintScreen,
"wakeup" => KeyCode::WakeUp,
"convert" => KeyCode::Convert,
"nonconvert" => KeyCode::NonConvert,
"hiragana" => KeyCode::Hiragana,
"katakana" => KeyCode::Katakana,
"f1" => KeyCode::F1,
"f2" => KeyCode::F2,
"f3" => KeyCode::F3,
"f4" => KeyCode::F4,
"f5" => KeyCode::F5,
"f6" => KeyCode::F6,
"f7" => KeyCode::F7,
"f8" => KeyCode::F8,
"f9" => KeyCode::F9,
"f10" => KeyCode::F10,
"f11" => KeyCode::F11,
"f12" => KeyCode::F12,
"mediastop" => KeyCode::MediaStop,
"open" => KeyCode::Open,
_ => {
return Err(anyhow::anyhow!(
"unrecognized physical key code {}",
&s[1..s.len() - 2]
))
}
};
KeyMapKey::Physical(PhysicalKey::Code(code))
} else {
let key = match s.to_lowercase().as_str() {
"esc" => Key::Named(NamedKey::Escape),
"space" => Key::Named(NamedKey::Space),
"bs" => Key::Named(NamedKey::Backspace),
"up" => Key::Named(NamedKey::ArrowUp),
"down" => Key::Named(NamedKey::ArrowDown),
"left" => Key::Named(NamedKey::ArrowLeft),
"right" => Key::Named(NamedKey::ArrowRight),
"del" => Key::Named(NamedKey::Delete),
"alt" => Key::Named(NamedKey::Alt),
"altgraph" => Key::Named(NamedKey::AltGraph),
"capslock" => Key::Named(NamedKey::CapsLock),
"control" => Key::Named(NamedKey::Control),
"fn" => Key::Named(NamedKey::Fn),
"fnlock" => Key::Named(NamedKey::FnLock),
"meta" => Key::Named(NamedKey::Meta),
"numlock" => Key::Named(NamedKey::NumLock),
"scrolllock" => Key::Named(NamedKey::ScrollLock),
"shift" => Key::Named(NamedKey::Shift),
"hyper" => Key::Named(NamedKey::Hyper),
"super" => Key::Named(NamedKey::Meta),
"enter" => Key::Named(NamedKey::Enter),
"tab" => Key::Named(NamedKey::Tab),
"arrowdown" => Key::Named(NamedKey::ArrowDown),
"arrowleft" => Key::Named(NamedKey::ArrowLeft),
"arrowright" => Key::Named(NamedKey::ArrowRight),
"arrowup" => Key::Named(NamedKey::ArrowUp),
"end" => Key::Named(NamedKey::End),
"home" => Key::Named(NamedKey::Home),
"pagedown" => Key::Named(NamedKey::PageDown),
"pageup" => Key::Named(NamedKey::PageUp),
"backspace" => Key::Named(NamedKey::Backspace),
"copy" => Key::Named(NamedKey::Copy),
"cut" => Key::Named(NamedKey::Cut),
"delete" => Key::Named(NamedKey::Delete),
"insert" => Key::Named(NamedKey::Insert),
"paste" => Key::Named(NamedKey::Paste),
"undo" => Key::Named(NamedKey::Undo),
"again" => Key::Named(NamedKey::Again),
"contextmenu" => Key::Named(NamedKey::ContextMenu),
"escape" => Key::Named(NamedKey::Escape),
"find" => Key::Named(NamedKey::Find),
"help" => Key::Named(NamedKey::Help),
"pause" => Key::Named(NamedKey::Pause),
"play" => Key::Named(NamedKey::MediaPlayPause),
"props" => Key::Named(NamedKey::Props),
"select" => Key::Named(NamedKey::Select),
"eject" => Key::Named(NamedKey::Eject),
"power" => Key::Named(NamedKey::Power),
"printscreen" => Key::Named(NamedKey::PrintScreen),
"wakeup" => Key::Named(NamedKey::WakeUp),
"convert" => Key::Named(NamedKey::Convert),
"nonconvert" => Key::Named(NamedKey::NonConvert),
"hiragana" => Key::Named(NamedKey::Hiragana),
"katakana" => Key::Named(NamedKey::Katakana),
"f1" => Key::Named(NamedKey::F1),
"f2" => Key::Named(NamedKey::F2),
"f3" => Key::Named(NamedKey::F3),
"f4" => Key::Named(NamedKey::F4),
"f5" => Key::Named(NamedKey::F5),
"f6" => Key::Named(NamedKey::F6),
"f7" => Key::Named(NamedKey::F7),
"f8" => Key::Named(NamedKey::F8),
"f9" => Key::Named(NamedKey::F9),
"f10" => Key::Named(NamedKey::F10),
"f11" => Key::Named(NamedKey::F11),
"f12" => Key::Named(NamedKey::F12),
"mediastop" => Key::Named(NamedKey::MediaStop),
"open" => Key::Named(NamedKey::Open),
_ => Key::Character(s.to_lowercase().into()),
};
KeyMapKey::Logical(key)
};
Ok(key)
}
}
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 self.mods.contains(Modifiers::ALT) {
let _ = f.write_str("Alt+");
}
if self.mods.contains(Modifiers::ALTGR) {
let _ = f.write_str("AltGr+");
}
if self.mods.contains(Modifiers::META) {
let _ = f.write_str("Meta+");
}
if self.mods.contains(Modifiers::SHIFT) {
let _ = f.write_str("Shift+");
}
f.write_str(&self.key.to_string())
}
}
impl Display for KeyMapKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use floem::pointer::PointerButton as B;
match self {
Self::Physical(physical) => {
f.write_str("[")?;
match physical {
PhysicalKey::Unidentified(_) => f.write_str("Unidentified"),
PhysicalKey::Code(KeyCode::Backquote) => {
f.write_str("Backquote")
}
PhysicalKey::Code(KeyCode::Backslash) => {
f.write_str("Backslash")
}
PhysicalKey::Code(KeyCode::BracketLeft) => {
f.write_str("BracketLeft")
}
PhysicalKey::Code(KeyCode::BracketRight) => {
f.write_str("BracketRight")
}
PhysicalKey::Code(KeyCode::Comma) => f.write_str("Comma"),
PhysicalKey::Code(KeyCode::Digit0) => f.write_str("0"),
PhysicalKey::Code(KeyCode::Digit1) => f.write_str("1"),
PhysicalKey::Code(KeyCode::Digit2) => f.write_str("2"),
PhysicalKey::Code(KeyCode::Digit3) => f.write_str("3"),
PhysicalKey::Code(KeyCode::Digit4) => f.write_str("4"),
PhysicalKey::Code(KeyCode::Digit5) => f.write_str("5"),
PhysicalKey::Code(KeyCode::Digit6) => f.write_str("6"),
PhysicalKey::Code(KeyCode::Digit7) => f.write_str("7"),
PhysicalKey::Code(KeyCode::Digit8) => f.write_str("8"),
PhysicalKey::Code(KeyCode::Digit9) => f.write_str("9"),
PhysicalKey::Code(KeyCode::Equal) => f.write_str("Equal"),
PhysicalKey::Code(KeyCode::IntlBackslash) => {
f.write_str("IntlBackslash")
}
PhysicalKey::Code(KeyCode::IntlRo) => f.write_str("IntlRo"),
PhysicalKey::Code(KeyCode::IntlYen) => f.write_str("IntlYen"),
PhysicalKey::Code(KeyCode::KeyA) => f.write_str("A"),
PhysicalKey::Code(KeyCode::KeyB) => f.write_str("B"),
PhysicalKey::Code(KeyCode::KeyC) => f.write_str("C"),
PhysicalKey::Code(KeyCode::KeyD) => f.write_str("D"),
PhysicalKey::Code(KeyCode::KeyE) => f.write_str("E"),
PhysicalKey::Code(KeyCode::KeyF) => f.write_str("F"),
PhysicalKey::Code(KeyCode::KeyG) => f.write_str("G"),
PhysicalKey::Code(KeyCode::KeyH) => f.write_str("H"),
PhysicalKey::Code(KeyCode::KeyI) => f.write_str("I"),
PhysicalKey::Code(KeyCode::KeyJ) => f.write_str("J"),
PhysicalKey::Code(KeyCode::KeyK) => f.write_str("K"),
PhysicalKey::Code(KeyCode::KeyL) => f.write_str("L"),
PhysicalKey::Code(KeyCode::KeyM) => f.write_str("M"),
PhysicalKey::Code(KeyCode::KeyN) => f.write_str("N"),
PhysicalKey::Code(KeyCode::KeyO) => f.write_str("O"),
PhysicalKey::Code(KeyCode::KeyP) => f.write_str("P"),
PhysicalKey::Code(KeyCode::KeyQ) => f.write_str("Q"),
PhysicalKey::Code(KeyCode::KeyR) => f.write_str("R"),
PhysicalKey::Code(KeyCode::KeyS) => f.write_str("S"),
PhysicalKey::Code(KeyCode::KeyT) => f.write_str("T"),
PhysicalKey::Code(KeyCode::KeyU) => f.write_str("U"),
PhysicalKey::Code(KeyCode::KeyV) => f.write_str("V"),
PhysicalKey::Code(KeyCode::KeyW) => f.write_str("W"),
PhysicalKey::Code(KeyCode::KeyX) => f.write_str("X"),
PhysicalKey::Code(KeyCode::KeyY) => f.write_str("Y"),
PhysicalKey::Code(KeyCode::KeyZ) => f.write_str("Z"),
PhysicalKey::Code(KeyCode::Minus) => f.write_str("Minus"),
PhysicalKey::Code(KeyCode::Period) => f.write_str("Period"),
PhysicalKey::Code(KeyCode::Quote) => f.write_str("Quote"),
PhysicalKey::Code(KeyCode::Semicolon) => {
f.write_str("Semicolon")
}
PhysicalKey::Code(KeyCode::Slash) => f.write_str("Slash"),
PhysicalKey::Code(KeyCode::AltLeft) => f.write_str("Alt"),
PhysicalKey::Code(KeyCode::AltRight) => f.write_str("Alt"),
PhysicalKey::Code(KeyCode::Backspace) => {
f.write_str("Backspace")
}
PhysicalKey::Code(KeyCode::CapsLock) => f.write_str("CapsLock"),
PhysicalKey::Code(KeyCode::ContextMenu) => {
f.write_str("ContextMenu")
}
PhysicalKey::Code(KeyCode::ControlLeft) => f.write_str("Ctrl"),
PhysicalKey::Code(KeyCode::ControlRight) => f.write_str("Ctrl"),
PhysicalKey::Code(KeyCode::Enter) => f.write_str("Enter"),
PhysicalKey::Code(KeyCode::SuperLeft) => f.write_str("Meta"),
PhysicalKey::Code(KeyCode::SuperRight) => f.write_str("Meta"),
PhysicalKey::Code(KeyCode::ShiftLeft) => f.write_str("Shift"),
PhysicalKey::Code(KeyCode::ShiftRight) => f.write_str("Shift"),
PhysicalKey::Code(KeyCode::Space) => f.write_str("Space"),
PhysicalKey::Code(KeyCode::Tab) => f.write_str("Tab"),
PhysicalKey::Code(KeyCode::Convert) => f.write_str("Convert"),
PhysicalKey::Code(KeyCode::KanaMode) => f.write_str("KanaMode"),
PhysicalKey::Code(KeyCode::Lang1) => f.write_str("Lang1"),
PhysicalKey::Code(KeyCode::Lang2) => f.write_str("Lang2"),
PhysicalKey::Code(KeyCode::Lang3) => f.write_str("Lang3"),
PhysicalKey::Code(KeyCode::Lang4) => f.write_str("Lang4"),
PhysicalKey::Code(KeyCode::Lang5) => f.write_str("Lang5"),
PhysicalKey::Code(KeyCode::NonConvert) => {
f.write_str("NonConvert")
}
PhysicalKey::Code(KeyCode::Delete) => f.write_str("Delete"),
PhysicalKey::Code(KeyCode::End) => f.write_str("End"),
PhysicalKey::Code(KeyCode::Help) => f.write_str("Help"),
PhysicalKey::Code(KeyCode::Home) => f.write_str("Home"),
PhysicalKey::Code(KeyCode::Insert) => f.write_str("Insert"),
PhysicalKey::Code(KeyCode::PageDown) => f.write_str("PageDown"),
PhysicalKey::Code(KeyCode::PageUp) => f.write_str("PageUp"),
PhysicalKey::Code(KeyCode::ArrowDown) => f.write_str("Down"),
PhysicalKey::Code(KeyCode::ArrowLeft) => f.write_str("Left"),
PhysicalKey::Code(KeyCode::ArrowRight) => f.write_str("Right"),
PhysicalKey::Code(KeyCode::ArrowUp) => f.write_str("Up"),
PhysicalKey::Code(KeyCode::NumLock) => f.write_str("NumLock"),
PhysicalKey::Code(KeyCode::Numpad0) => f.write_str("Numpad0"),
PhysicalKey::Code(KeyCode::Numpad1) => f.write_str("Numpad1"),
PhysicalKey::Code(KeyCode::Numpad2) => f.write_str("Numpad2"),
PhysicalKey::Code(KeyCode::Numpad3) => f.write_str("Numpad3"),
PhysicalKey::Code(KeyCode::Numpad4) => f.write_str("Numpad4"),
PhysicalKey::Code(KeyCode::Numpad5) => f.write_str("Numpad5"),
PhysicalKey::Code(KeyCode::Numpad6) => f.write_str("Numpad6"),
PhysicalKey::Code(KeyCode::Numpad7) => f.write_str("Numpad7"),
PhysicalKey::Code(KeyCode::Numpad8) => f.write_str("Numpad8"),
PhysicalKey::Code(KeyCode::Numpad9) => f.write_str("Numpad9"),
PhysicalKey::Code(KeyCode::NumpadAdd) => {
f.write_str("NumpadAdd")
}
PhysicalKey::Code(KeyCode::NumpadBackspace) => {
f.write_str("NumpadBackspace")
}
PhysicalKey::Code(KeyCode::NumpadClear) => {
f.write_str("NumpadClear")
}
PhysicalKey::Code(KeyCode::NumpadClearEntry) => {
f.write_str("NumpadClearEntry")
}
PhysicalKey::Code(KeyCode::NumpadComma) => {
f.write_str("NumpadComma")
}
PhysicalKey::Code(KeyCode::NumpadDecimal) => {
f.write_str("NumpadDecimal")
}
PhysicalKey::Code(KeyCode::NumpadDivide) => {
f.write_str("NumpadDivide")
}
PhysicalKey::Code(KeyCode::NumpadEnter) => {
f.write_str("NumpadEnter")
}
PhysicalKey::Code(KeyCode::NumpadEqual) => {
f.write_str("NumpadEqual")
}
PhysicalKey::Code(KeyCode::NumpadHash) => {
f.write_str("NumpadHash")
}
PhysicalKey::Code(KeyCode::NumpadMemoryAdd) => {
f.write_str("NumpadMemoryAdd")
}
PhysicalKey::Code(KeyCode::NumpadMemoryClear) => {
f.write_str("NumpadMemoryClear")
}
PhysicalKey::Code(KeyCode::NumpadMemoryRecall) => {
f.write_str("NumpadMemoryRecall")
}
PhysicalKey::Code(KeyCode::NumpadMemoryStore) => {
f.write_str("NumpadMemoryStore")
}
PhysicalKey::Code(KeyCode::NumpadMemorySubtract) => {
f.write_str("NumpadMemorySubtract")
}
PhysicalKey::Code(KeyCode::NumpadMultiply) => {
f.write_str("NumpadMultiply")
}
PhysicalKey::Code(KeyCode::NumpadParenLeft) => {
f.write_str("NumpadParenLeft")
}
PhysicalKey::Code(KeyCode::NumpadParenRight) => {
f.write_str("NumpadParenRight")
}
PhysicalKey::Code(KeyCode::NumpadStar) => {
f.write_str("NumpadStar")
}
PhysicalKey::Code(KeyCode::NumpadSubtract) => {
f.write_str("NumpadSubtract")
}
PhysicalKey::Code(KeyCode::Escape) => f.write_str("Escape"),
PhysicalKey::Code(KeyCode::Fn) => f.write_str("Fn"),
PhysicalKey::Code(KeyCode::FnLock) => f.write_str("FnLock"),
PhysicalKey::Code(KeyCode::PrintScreen) => {
f.write_str("PrintScreen")
}
PhysicalKey::Code(KeyCode::ScrollLock) => {
f.write_str("ScrollLock")
}
PhysicalKey::Code(KeyCode::Pause) => f.write_str("Pause"),
PhysicalKey::Code(KeyCode::BrowserBack) => {
f.write_str("BrowserBack")
}
PhysicalKey::Code(KeyCode::BrowserFavorites) => {
f.write_str("BrowserFavorites")
}
PhysicalKey::Code(KeyCode::BrowserForward) => {
f.write_str("BrowserForward")
}
PhysicalKey::Code(KeyCode::BrowserHome) => {
f.write_str("BrowserHome")
}
PhysicalKey::Code(KeyCode::BrowserRefresh) => {
f.write_str("BrowserRefresh")
}
PhysicalKey::Code(KeyCode::BrowserSearch) => {
f.write_str("BrowserSearch")
}
PhysicalKey::Code(KeyCode::BrowserStop) => {
f.write_str("BrowserStop")
}
PhysicalKey::Code(KeyCode::Eject) => f.write_str("Eject"),
PhysicalKey::Code(KeyCode::LaunchApp1) => {
f.write_str("LaunchApp1")
}
PhysicalKey::Code(KeyCode::LaunchApp2) => {
f.write_str("LaunchApp2")
}
PhysicalKey::Code(KeyCode::LaunchMail) => {
f.write_str("LaunchMail")
}
PhysicalKey::Code(KeyCode::MediaPlayPause) => {
f.write_str("MediaPlayPause")
}
PhysicalKey::Code(KeyCode::MediaSelect) => {
f.write_str("MediaSelect")
}
PhysicalKey::Code(KeyCode::MediaStop) => {
f.write_str("MediaStop")
}
PhysicalKey::Code(KeyCode::MediaTrackNext) => {
f.write_str("MediaTrackNext")
}
PhysicalKey::Code(KeyCode::MediaTrackPrevious) => {
f.write_str("MediaTrackPrevious")
}
PhysicalKey::Code(KeyCode::Power) => f.write_str("Power"),
PhysicalKey::Code(KeyCode::Sleep) => f.write_str("Sleep"),
PhysicalKey::Code(KeyCode::AudioVolumeDown) => {
f.write_str("AudioVolumeDown")
}
PhysicalKey::Code(KeyCode::AudioVolumeMute) => {
f.write_str("AudioVolumeMute")
}
PhysicalKey::Code(KeyCode::AudioVolumeUp) => {
f.write_str("AudioVolumeUp")
}
PhysicalKey::Code(KeyCode::WakeUp) => f.write_str("WakeUp"),
PhysicalKey::Code(KeyCode::Meta) => match std::env::consts::OS {
"macos" => f.write_str("Cmd"),
"windows" => f.write_str("Win"),
_ => f.write_str("Meta"),
},
PhysicalKey::Code(KeyCode::Hyper) => f.write_str("Hyper"),
PhysicalKey::Code(KeyCode::Turbo) => f.write_str("Turbo"),
PhysicalKey::Code(KeyCode::Abort) => f.write_str("Abort"),
PhysicalKey::Code(KeyCode::Resume) => f.write_str("Resume"),
PhysicalKey::Code(KeyCode::Suspend) => f.write_str("Suspend"),
PhysicalKey::Code(KeyCode::Again) => f.write_str("Again"),
PhysicalKey::Code(KeyCode::Copy) => f.write_str("Copy"),
PhysicalKey::Code(KeyCode::Cut) => f.write_str("Cut"),
PhysicalKey::Code(KeyCode::Find) => f.write_str("Find"),
PhysicalKey::Code(KeyCode::Open) => f.write_str("Open"),
PhysicalKey::Code(KeyCode::Paste) => f.write_str("Paste"),
PhysicalKey::Code(KeyCode::Props) => f.write_str("Props"),
PhysicalKey::Code(KeyCode::Select) => f.write_str("Select"),
PhysicalKey::Code(KeyCode::Undo) => f.write_str("Undo"),
PhysicalKey::Code(KeyCode::Hiragana) => f.write_str("Hiragana"),
PhysicalKey::Code(KeyCode::Katakana) => f.write_str("Katakana"),
PhysicalKey::Code(KeyCode::F1) => f.write_str("F1"),
PhysicalKey::Code(KeyCode::F2) => f.write_str("F2"),
PhysicalKey::Code(KeyCode::F3) => f.write_str("F3"),
PhysicalKey::Code(KeyCode::F4) => f.write_str("F4"),
PhysicalKey::Code(KeyCode::F5) => f.write_str("F5"),
PhysicalKey::Code(KeyCode::F6) => f.write_str("F6"),
PhysicalKey::Code(KeyCode::F7) => f.write_str("F7"),
PhysicalKey::Code(KeyCode::F8) => f.write_str("F8"),
PhysicalKey::Code(KeyCode::F9) => f.write_str("F9"),
PhysicalKey::Code(KeyCode::F10) => f.write_str("F10"),
PhysicalKey::Code(KeyCode::F11) => f.write_str("F11"),
PhysicalKey::Code(KeyCode::F12) => f.write_str("F12"),
PhysicalKey::Code(KeyCode::F13) => f.write_str("F13"),
PhysicalKey::Code(KeyCode::F14) => f.write_str("F14"),
PhysicalKey::Code(KeyCode::F15) => f.write_str("F15"),
PhysicalKey::Code(KeyCode::F16) => f.write_str("F16"),
PhysicalKey::Code(KeyCode::F17) => f.write_str("F17"),
PhysicalKey::Code(KeyCode::F18) => f.write_str("F18"),
PhysicalKey::Code(KeyCode::F19) => f.write_str("F19"),
PhysicalKey::Code(KeyCode::F20) => f.write_str("F20"),
PhysicalKey::Code(KeyCode::F21) => f.write_str("F21"),
PhysicalKey::Code(KeyCode::F22) => f.write_str("F22"),
PhysicalKey::Code(KeyCode::F23) => f.write_str("F23"),
PhysicalKey::Code(KeyCode::F24) => f.write_str("F24"),
PhysicalKey::Code(KeyCode::F25) => f.write_str("F25"),
PhysicalKey::Code(KeyCode::F26) => f.write_str("F26"),
PhysicalKey::Code(KeyCode::F27) => f.write_str("F27"),
PhysicalKey::Code(KeyCode::F28) => f.write_str("F28"),
PhysicalKey::Code(KeyCode::F29) => f.write_str("F29"),
PhysicalKey::Code(KeyCode::F30) => f.write_str("F30"),
PhysicalKey::Code(KeyCode::F31) => f.write_str("F31"),
PhysicalKey::Code(KeyCode::F32) => f.write_str("F32"),
PhysicalKey::Code(KeyCode::F33) => f.write_str("F33"),
PhysicalKey::Code(KeyCode::F34) => f.write_str("F34"),
PhysicalKey::Code(KeyCode::F35) => f.write_str("F35"),
_ => f.write_str("Unidentified"),
}?;
f.write_str("]")
}
Self::Logical(key) => match key {
Key::Named(key) => match key {
NamedKey::Backspace => f.write_str("Backspace"),
NamedKey::CapsLock => f.write_str("CapsLock"),
NamedKey::Enter => f.write_str("Enter"),
NamedKey::Delete => f.write_str("Delete"),
NamedKey::End => f.write_str("End"),
NamedKey::Home => f.write_str("Home"),
NamedKey::PageDown => f.write_str("PageDown"),
NamedKey::PageUp => f.write_str("PageUp"),
NamedKey::ArrowDown => f.write_str("ArrowDown"),
NamedKey::ArrowUp => f.write_str("ArrowUp"),
NamedKey::ArrowLeft => f.write_str("ArrowLeft"),
NamedKey::ArrowRight => f.write_str("ArrowRight"),
NamedKey::Escape => f.write_str("Escape"),
NamedKey::Fn => f.write_str("Fn"),
NamedKey::Space => f.write_str("Space"),
NamedKey::Shift => f.write_str("Shift"),
NamedKey::Meta => f.write_str("Meta"),
NamedKey::Super => f.write_str("Meta"),
NamedKey::Control => f.write_str("Ctrl"),
NamedKey::Alt => f.write_str("Alt"),
NamedKey::AltGraph => f.write_str("AltGraph"),
NamedKey::Tab => f.write_str("Tab"),
NamedKey::F1 => f.write_str("F1"),
NamedKey::F2 => f.write_str("F2"),
NamedKey::F3 => f.write_str("F3"),
NamedKey::F4 => f.write_str("F4"),
NamedKey::F5 => f.write_str("F5"),
NamedKey::F6 => f.write_str("F6"),
NamedKey::F7 => f.write_str("F7"),
NamedKey::F8 => f.write_str("F8"),
NamedKey::F9 => f.write_str("F9"),
NamedKey::F10 => f.write_str("F10"),
NamedKey::F11 => f.write_str("F11"),
NamedKey::F12 => f.write_str("F12"),
_ => f.write_str("Unidentified"),
},
Key::Character(s) => f.write_str(s),
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(_) => f.write_str("MouseUnimplemented"),
}
}
}
+44 -29
View File
@@ -3,10 +3,10 @@ use indexmap::IndexMap;
use lapce_core::mode::Modes;
use tracing::{debug, error};
use super::{keymap::KeyMap, press::KeyPress};
use super::keymap::{KeyMap, KeyMapPress};
pub struct KeyMapLoader {
keymaps: IndexMap<Vec<KeyPress>, Vec<KeyMap>>,
keymaps: IndexMap<Vec<KeyMapPress>, Vec<KeyMap>>,
command_keymaps: IndexMap<String, Vec<KeyMap>>,
}
@@ -80,7 +80,7 @@ impl KeyMapLoader {
pub fn finalize(
self,
) -> (
IndexMap<Vec<KeyPress>, Vec<KeyMap>>,
IndexMap<Vec<KeyMapPress>, Vec<KeyMap>>,
IndexMap<String, Vec<KeyMap>>,
) {
let Self {
@@ -112,7 +112,7 @@ impl KeyMapLoader {
}
Ok(Some(KeyMap {
key: KeyPress::parse(key),
key: KeyMapPress::parse(key),
modes,
when: toml_keymap
.get("when")
@@ -122,18 +122,29 @@ impl KeyMapLoader {
.get("command")
.and_then(|c| c.as_str())
.map(|w| w.trim().to_string())
.unwrap_or_else(|| "".to_string()),
.unwrap_or_default(),
}))
}
}
fn get_modes(toml_keymap: &toml_edit::Table) -> Modes {
toml_keymap
.get("mode")
.and_then(|v| v.as_str())
.map(Modes::parse)
.unwrap_or_else(Modes::empty)
}
#[cfg(test)]
mod tests {
use floem::keyboard::Key;
use super::*;
use crate::keypress::keymap::KeyMapKey;
#[test]
fn test_keymap() {
let keymaps = r###"
let keymaps = r#"
[[keymaps]]
key = "ctrl+w l l"
command = "right"
@@ -175,64 +186,68 @@ command = "jump_location_backward"
[[keymaps]]
key = "Ctrl+MouseMiddle"
command = "goto_definition"
"###;
"#;
let mut loader = KeyMapLoader::new();
loader.load_from_str(keymaps, true).unwrap();
let (keymaps, _) = loader.finalize();
// Lower case modifiers
let keypress = KeyPress::parse("ctrl+w");
let keypress = KeyMapPress::parse("ctrl+w");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 4);
let keypress = KeyPress::parse("ctrl+w l");
let keypress = KeyMapPress::parse("ctrl+w l");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 2);
let keypress = KeyPress::parse("ctrl+w h");
let keypress = KeyMapPress::parse("ctrl+w h");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("ctrl+w l l");
let keypress = KeyMapPress::parse("ctrl+w l l");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("end");
let keypress = KeyMapPress::parse("end");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
// Upper case modifiers
let keypress = KeyPress::parse("Ctrl+w");
let keypress = KeyMapPress::parse("Ctrl+w");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 4);
let keypress = KeyPress::parse("Ctrl+w l");
let keypress = KeyMapPress::parse("Ctrl+w l");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 2);
let keypress = KeyPress::parse("Ctrl+w h");
let keypress = KeyMapPress::parse("Ctrl+w h");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("Ctrl+w l l");
let keypress = KeyMapPress::parse("Ctrl+w l l");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("End");
let keypress = KeyMapPress::parse("End");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
// No modifier
let keypress = KeyPress::parse("shift+i");
let keypress = KeyMapPress::parse("shift+i");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
// Mouse keys
let keypress = KeyPress::parse("MouseForward");
let keypress = KeyMapPress::parse("MouseForward");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("mousebackward");
let keypress = KeyMapPress::parse("mousebackward");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyPress::parse("Ctrl+MouseMiddle");
let keypress = KeyMapPress::parse("Ctrl+MouseMiddle");
assert_eq!(keymaps.get(&keypress).unwrap().len(), 1);
let keypress = KeyMapPress::parse("Ctrl++");
assert_eq!(
keypress[0].key,
KeyMapKey::Logical(Key::Character("+".into()))
);
let keypress = KeyMapPress::parse("+");
assert_eq!(
keypress[0].key,
KeyMapKey::Logical(Key::Character("+".into()))
);
}
}
fn get_modes(toml_keymap: &toml_edit::Table) -> Modes {
toml_keymap
.get("mode")
.and_then(|v| v.as_str())
.map(Modes::parse)
.unwrap_or_else(Modes::empty)
}
+7 -102
View File
@@ -1,113 +1,18 @@
use std::fmt::Display;
use floem::keyboard::Modifiers;
use floem::glazier::{KbKey, Modifiers};
use tracing::warn;
use super::{key::KeyInput, keymap::KeyMapPress};
use super::key::Key;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Debug)]
pub struct KeyPress {
pub(super) key: Key,
pub(super) key: KeyInput,
pub(super) mods: Modifiers,
}
impl KeyPress {
pub fn to_lowercase(&self) -> Self {
let key = match &self.key {
Key::Keyboard(KbKey::Character(c)) => {
Key::Keyboard(KbKey::Character(c.to_lowercase()))
}
_ => self.key.clone(),
};
Self {
key,
pub fn keymap_press(&self) -> KeyMapPress {
KeyMapPress {
key: self.key.keymap_key(),
mods: self.mods,
}
}
pub fn is_char(&self) -> bool {
let mut mods = self.mods;
mods.set(Modifiers::SHIFT, false);
if mods.is_empty() {
if let Key::Keyboard(KbKey::Character(_c)) = &self.key {
return true;
}
}
false
}
pub fn label(&self) -> String {
let mut keys = String::from("");
if self.mods.contains(Modifiers::CONTROL) {
keys.push_str("Ctrl+");
}
if self.mods.contains(Modifiers::ALT) {
keys.push_str("Alt+");
}
if self.mods.contains(Modifiers::META) {
let keyname = match std::env::consts::OS {
"macos" => "Cmd+",
"windows" => "Win+",
_ => "Meta+",
};
keys.push_str(keyname);
}
if self.mods.contains(Modifiers::SHIFT) {
keys.push_str("Shift+");
}
keys.push_str(&self.key.to_string());
keys.trim().to_string()
}
pub fn parse(key: &str) -> Vec<Self> {
key.split(' ')
.filter_map(|k| {
let (modifiers, key) = match k.rsplit_once('+') {
Some(pair) => pair,
None => ("", k),
};
let key = match key.parse().ok() {
Some(key) => key,
None => {
// Skip past unrecognized key definitions
warn!("Unrecognized key: {key}");
return None;
}
};
let mut mods = Modifiers::default();
for part in modifiers.to_lowercase().split('+') {
match part {
"ctrl" => mods.set(Modifiers::CONTROL, true),
"meta" => mods.set(Modifiers::META, true),
"shift" => mods.set(Modifiers::SHIFT, true),
"alt" => mods.set(Modifiers::ALT, true),
"" => (),
other => warn!("Invalid key modifier: {}", other),
}
}
Some(KeyPress { key, mods })
})
.collect()
}
}
impl Display for KeyPress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.mods.contains(Modifiers::CONTROL) {
let _ = f.write_str("Ctrl+");
}
if self.mods.contains(Modifiers::ALT) {
let _ = f.write_str("Alt+");
}
if self.mods.contains(Modifiers::META) {
let _ = f.write_str("Meta+");
}
if self.mods.contains(Modifiers::SHIFT) {
let _ = f.write_str("Shift+");
}
f.write_str(&self.key.to_string())
}
}
+8
View File
@@ -15,11 +15,15 @@ pub mod find;
pub mod focus_text;
pub mod global_search;
pub mod history;
pub mod hover;
pub mod id;
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;
pub mod panel;
pub mod plugin;
@@ -28,6 +32,7 @@ pub mod rename;
pub mod settings;
pub mod snippet;
pub mod source_control;
pub mod status;
pub mod terminal;
pub mod text_area;
pub mod text_input;
@@ -38,3 +43,6 @@ pub mod web_link;
pub mod window;
pub mod window_tab;
pub mod workspace;
#[cfg(windows)]
extern crate windows_sys as windows;
+13 -6
View File
@@ -28,11 +28,21 @@ impl<T: Clone + 'static> Listener<T> {
Listener { val, cx }
}
/// Listen for values sent to this listener.
pub fn scope(&self) -> Scope {
self.cx
}
/// Listen for values sent to this listener.
pub fn listen(self, on_val: impl Fn(T) + 'static) {
self.listen_with(self.cx, on_val)
}
/// Listen for values sent to this listener.
/// Allows creating the effect with a custom scope, letting it be disposed of.
pub fn listen_with(self, cx: Scope, on_val: impl Fn(T) + 'static) {
let val = self.val;
self.cx.create_effect(move |_| {
cx.create_effect(move |_| {
// TODO(minor): Signals could have a `take` method to avoid cloning.
if let Some(cmd) = val.get() {
on_val(cmd);
@@ -50,9 +60,6 @@ impl<T: 'static> Copy for Listener<T> {}
impl<T: 'static> Clone for Listener<T> {
fn clone(&self) -> Self {
Listener {
cx: self.cx,
val: self.val,
}
*self
}
}
+89
View File
@@ -0,0 +1,89 @@
use std::path::PathBuf;
use tracing::{event, Level};
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)
})
}
+1273 -482
View File
File diff suppressed because it is too large Load Diff
+340
View File
@@ -0,0 +1,340 @@
use floem::cosmic_text::{
Attrs, AttrsList, FamilyOwned, LineHeightValue, Style, TextLayout, Weight,
};
use lapce_core::{language::LapceLanguage, syntax::Syntax};
use lapce_xi_rope::Rope;
use lsp_types::MarkedString;
use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use smallvec::SmallVec;
use crate::config::{color::LapceColor, LapceConfig};
#[derive(Clone)]
pub enum MarkdownContent {
Text(TextLayout),
Image { url: String, title: String },
Separator,
}
pub fn parse_markdown(
text: &str,
line_height: f64,
config: &LapceConfig,
) -> Vec<MarkdownContent> {
let mut res = Vec::new();
let mut current_text = String::new();
let code_font_family: Vec<FamilyOwned> =
FamilyOwned::parse_list(&config.editor.font_family).collect();
let default_attrs = Attrs::new()
.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 builder_dirty = false;
let mut pos = 0;
let mut tag_stack: SmallVec<[(usize, Tag); 4]> = SmallVec::new();
let parser = Parser::new_ext(
text,
Options::ENABLE_TABLES
| Options::ENABLE_FOOTNOTES
| Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_TASKLISTS
| Options::ENABLE_HEADING_ATTRIBUTES,
);
let mut last_text = CowStr::from("");
// Whether we should add a newline on the next entry
// This is used so that we don't emit newlines at the very end of the generation
let mut add_newline = false;
for event in parser {
// Add the newline since we're going to be outputting more
if add_newline {
current_text.push('\n');
builder_dirty = true;
pos += 1;
add_newline = false;
}
match event {
Event::Start(tag) => {
tag_stack.push((pos, tag));
}
Event::End(end_tag) => {
if let Some((start_offset, tag)) = tag_stack.pop() {
if end_tag != tag.to_end() {
tracing::warn!("Mismatched markdown tag");
continue;
}
if let Some(attrs) = attribute_for_tag(
default_attrs,
&tag,
&code_font_family,
config,
) {
attr_list
.add_span(start_offset..pos.max(start_offset), attrs);
}
if should_add_newline_after_tag(&tag) {
add_newline = true;
}
match &tag {
Tag::CodeBlock(kind) => {
let language =
if let CodeBlockKind::Fenced(language) = kind {
md_language_to_lapce_language(language)
} else {
None
};
highlight_as_code(
&mut attr_list,
default_attrs.family(&code_font_family),
language,
&last_text,
start_offset,
config,
);
builder_dirty = true;
}
Tag::Image {
link_type: _,
dest_url: dest,
title,
id: _,
} => {
// TODO: Are there any link types that would change how the
// image is rendered?
if builder_dirty {
let mut text_layout = TextLayout::new();
text_layout.set_text(&current_text, attr_list);
res.push(MarkdownContent::Text(text_layout));
attr_list = AttrsList::new(default_attrs);
current_text.clear();
pos = 0;
builder_dirty = false;
}
res.push(MarkdownContent::Image {
url: dest.to_string(),
title: title.to_string(),
});
}
_ => {
// Presumably?
builder_dirty = true;
}
}
} else {
tracing::warn!("Unbalanced markdown tag")
}
}
Event::Text(text) => {
if let Some((_, tag)) = tag_stack.last() {
if should_skip_text_in_tag(tag) {
continue;
}
}
current_text.push_str(&text);
pos += text.len();
last_text = text;
builder_dirty = true;
}
Event::Code(text) => {
attr_list.add_span(
pos..pos + text.len(),
default_attrs.family(&code_font_family),
);
current_text.push_str(&text);
pos += text.len();
builder_dirty = true;
}
// TODO: Some minimal 'parsing' of html could be useful here, since some things use
// basic html like `<code>text</code>`.
Event::Html(text) => {
attr_list.add_span(
pos..pos + text.len(),
default_attrs
.family(&code_font_family)
.color(config.color(LapceColor::MARKDOWN_BLOCKQUOTE)),
);
current_text.push_str(&text);
pos += text.len();
builder_dirty = true;
}
Event::HardBreak => {
current_text.push('\n');
pos += 1;
builder_dirty = true;
}
Event::SoftBreak => {
current_text.push(' ');
pos += 1;
builder_dirty = true;
}
Event::Rule => {}
Event::FootnoteReference(_text) => {}
Event::TaskListMarker(_text) => {}
Event::InlineHtml(_) => {} // TODO(panekj): Implement
}
}
if builder_dirty {
let mut text_layout = TextLayout::new();
text_layout.set_text(&current_text, attr_list);
res.push(MarkdownContent::Text(text_layout));
}
res
}
fn attribute_for_tag<'a>(
default_attrs: Attrs<'a>,
tag: &Tag,
code_font_family: &'a [FamilyOwned],
config: &LapceConfig,
) -> Option<Attrs<'a>> {
use pulldown_cmark::HeadingLevel;
match tag {
Tag::Heading {
level,
id: _,
classes: _,
attrs: _,
} => {
// The size calculations are based on the em values given at
// https://drafts.csswg.org/css2/#html-stylesheet
let font_scale = match level {
HeadingLevel::H1 => 2.0,
HeadingLevel::H2 => 1.5,
HeadingLevel::H3 => 1.17,
HeadingLevel::H4 => 1.0,
HeadingLevel::H5 => 0.83,
HeadingLevel::H6 => 0.75,
};
let font_size = font_scale * config.ui.font_size() as f64;
Some(
default_attrs
.font_size(font_size as f32)
.weight(Weight::BOLD),
)
}
Tag::BlockQuote => Some(
default_attrs
.style(Style::Italic)
.color(config.color(LapceColor::MARKDOWN_BLOCKQUOTE)),
),
Tag::CodeBlock(_) => Some(default_attrs.family(code_font_family)),
Tag::Emphasis => Some(default_attrs.style(Style::Italic)),
Tag::Strong => Some(default_attrs.weight(Weight::BOLD)),
// TODO: Strikethrough support
Tag::Link {
link_type: _,
dest_url: _,
title: _,
id: _,
} => {
// TODO: Link support
Some(default_attrs.color(config.color(LapceColor::EDITOR_LINK)))
}
// All other tags are currently ignored
_ => None,
}
}
/// Decides whether newlines should be added after a specific markdown tag
fn should_add_newline_after_tag(tag: &Tag) -> bool {
!matches!(
tag,
Tag::Emphasis | Tag::Strong | Tag::Strikethrough | Tag::Link { .. }
)
}
/// Whether it should skip the text node after a specific tag
/// For example, images are skipped because it emits their title as a separate text node.
fn should_skip_text_in_tag(tag: &Tag) -> bool {
matches!(tag, Tag::Image { .. })
}
fn md_language_to_lapce_language(lang: &str) -> Option<LapceLanguage> {
// TODO: There are many other names commonly used that should be supported
LapceLanguage::from_name(lang)
}
/// Highlight the text in a richtext builder like it was a markdown codeblock
pub fn highlight_as_code(
attr_list: &mut AttrsList,
default_attrs: Attrs,
language: Option<LapceLanguage>,
text: &str,
start_offset: usize,
config: &LapceConfig,
) {
let syntax = language.map(Syntax::from_language);
let styles = syntax
.map(|mut syntax| {
syntax.parse(0, Rope::from(text), None);
syntax.styles
})
.unwrap_or(None);
if let Some(styles) = styles {
for (range, style) in styles.iter() {
if let Some(color) = style
.fg_color
.as_ref()
.and_then(|fg| config.style_color(fg))
{
attr_list.add_span(
start_offset + range.start..start_offset + range.end,
default_attrs.color(color),
);
}
}
}
}
pub fn from_marked_string(
text: MarkedString,
config: &LapceConfig,
) -> Vec<MarkdownContent> {
match text {
MarkedString::String(text) => parse_markdown(&text, 1.5, 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,
config,
)
}
}
}
pub fn from_plaintext(
text: &str,
line_height: f64,
config: &LapceConfig,
) -> Vec<MarkdownContent> {
let mut text_layout = TextLayout::new();
text_layout.set_text(
text,
AttrsList::new(
Attrs::new()
.font_size(config.ui.font_size() as f32)
.line_height(LineHeightValue::Normal(line_height as f32)),
),
);
vec![MarkdownContent::Text(text_layout)]
}
+468 -177
View File
File diff suppressed because it is too large Load Diff
+17 -2
View File
@@ -1,10 +1,11 @@
use std::path::PathBuf;
use lapce_core::line_ending::LineEnding;
use lapce_rpc::dap_types::RunDebugConfig;
use lsp_types::{Range, SymbolKind};
use crate::{
command::LapceCommand,
command::{LapceCommand, LapceWorkbenchCommand},
debug::RunDebugMode,
editor::location::EditorLocation,
workspace::{LapceWorkspace, SshHost},
@@ -14,12 +15,15 @@ use crate::{
pub struct PaletteItem {
pub content: PaletteItemContent,
pub filter_text: String,
pub score: i64,
pub score: u32,
pub indices: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum PaletteItemContent {
PaletteHelp {
cmd: LapceWorkbenchCommand,
},
File {
path: PathBuf,
full_path: PathBuf,
@@ -53,6 +57,10 @@ pub enum PaletteItemContent {
SshHost {
host: SshHost,
},
#[cfg(windows)]
WslHost {
host: crate::workspace::WslHost,
},
RunAndDebug {
mode: RunDebugMode,
config: RunDebugConfig,
@@ -66,7 +74,14 @@ pub enum PaletteItemContent {
Language {
name: String,
},
LineEnding {
kind: LineEnding,
},
SCMReference {
name: String,
},
TerminalProfile {
name: String,
profile: lapce_rpc::terminal::TerminalProfile,
},
}
+64 -5
View File
@@ -1,5 +1,10 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
use strum_macros::EnumIter;
use crate::command::LapceWorkbenchCommand;
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
pub enum PaletteKind {
PaletteHelp,
File,
Line,
Command,
@@ -8,23 +13,30 @@ pub enum PaletteKind {
DocumentSymbol,
WorkspaceSymbol,
SshHost,
#[cfg(windows)]
WslHost,
RunAndDebug,
ColorTheme,
IconTheme,
Language,
LineEnding,
SCMReferences,
TerminalProfile,
DiffFiles,
}
impl PaletteKind {
/// The symbol/prefix that is used to signify the behavior of the palette.
pub fn symbol(&self) -> &'static str {
match &self {
PaletteKind::PaletteHelp => "?",
PaletteKind::Line => "/",
PaletteKind::DocumentSymbol => "@",
PaletteKind::WorkspaceSymbol => "#",
// PaletteKind::GlobalSearch => "?",
PaletteKind::Workspace => ">",
PaletteKind::Command => ":",
PaletteKind::TerminalProfile => "<",
PaletteKind::File
| PaletteKind::Reference
| PaletteKind::SshHost
@@ -32,22 +44,63 @@ impl PaletteKind {
| PaletteKind::ColorTheme
| PaletteKind::IconTheme
| PaletteKind::Language
| PaletteKind::SCMReferences => "",
| PaletteKind::LineEnding
| PaletteKind::SCMReferences
| PaletteKind::DiffFiles => "",
#[cfg(windows)]
PaletteKind::WslHost => "",
}
}
/// Extract the palette kind from the input string. This is most often a prefix.
pub fn from_input(input: &str) -> PaletteKind {
match input {
_ if input.starts_with('?') => PaletteKind::PaletteHelp,
_ if input.starts_with('/') => PaletteKind::Line,
_ if input.starts_with('@') => PaletteKind::DocumentSymbol,
_ if input.starts_with('#') => PaletteKind::WorkspaceSymbol,
_ if input.starts_with('>') => PaletteKind::Workspace,
_ if input.starts_with(':') => PaletteKind::Command,
_ if input.starts_with('<') => PaletteKind::TerminalProfile,
_ => PaletteKind::File,
}
}
/// Get the [`LapceWorkbenchCommand`] that opens this palette kind, if one exists.
pub fn command(self) -> Option<LapceWorkbenchCommand> {
match self {
PaletteKind::PaletteHelp => Some(LapceWorkbenchCommand::PaletteHelp),
PaletteKind::Line => Some(LapceWorkbenchCommand::PaletteLine),
PaletteKind::DocumentSymbol => {
Some(LapceWorkbenchCommand::PaletteSymbol)
}
PaletteKind::WorkspaceSymbol => {
Some(LapceWorkbenchCommand::PaletteWorkspaceSymbol)
}
PaletteKind::Workspace => Some(LapceWorkbenchCommand::PaletteWorkspace),
PaletteKind::Command => Some(LapceWorkbenchCommand::PaletteCommand),
PaletteKind::File => Some(LapceWorkbenchCommand::Palette),
PaletteKind::Reference => None, // InternalCommand::PaletteReferences
PaletteKind::SshHost => Some(LapceWorkbenchCommand::ConnectSshHost),
#[cfg(windows)]
PaletteKind::WslHost => Some(LapceWorkbenchCommand::ConnectWslHost),
PaletteKind::RunAndDebug => {
Some(LapceWorkbenchCommand::PaletteRunAndDebug)
}
PaletteKind::ColorTheme => Some(LapceWorkbenchCommand::ChangeColorTheme),
PaletteKind::IconTheme => Some(LapceWorkbenchCommand::ChangeIconTheme),
PaletteKind::Language => Some(LapceWorkbenchCommand::ChangeFileLanguage),
PaletteKind::LineEnding => {
Some(LapceWorkbenchCommand::ChangeFileLineEnding)
}
PaletteKind::SCMReferences => {
Some(LapceWorkbenchCommand::PaletteSCMReferences)
}
PaletteKind::TerminalProfile => None, // InternalCommand::NewTerminal
PaletteKind::DiffFiles => Some(LapceWorkbenchCommand::DiffFiles),
}
}
// pub fn has_preview(&self) -> bool {
// matches!(
// self,
@@ -61,6 +114,8 @@ impl PaletteKind {
pub fn get_input<'a>(&self, input: &'a str) -> &'a str {
match self {
#[cfg(windows)]
PaletteKind::WslHost => input,
PaletteKind::File
| PaletteKind::Reference
| PaletteKind::SshHost
@@ -68,14 +123,18 @@ impl PaletteKind {
| PaletteKind::ColorTheme
| PaletteKind::IconTheme
| PaletteKind::Language
| PaletteKind::SCMReferences => input,
PaletteKind::Command
| PaletteKind::LineEnding
| PaletteKind::SCMReferences
| PaletteKind::DiffFiles => input,
PaletteKind::PaletteHelp
| PaletteKind::Command
| PaletteKind::Workspace
| PaletteKind::DocumentSymbol
| PaletteKind::WorkspaceSymbol
| PaletteKind::Line
| PaletteKind::TerminalProfile
// | PaletteType::GlobalSearch
=> if !input.is_empty() {&input[1..]} else {input},
=> input.get(1..).unwrap_or(""),
}
}
+56 -4
View File
@@ -1,6 +1,9 @@
use std::sync::Arc;
use std::{rc::Rc, sync::Arc};
use floem::reactive::{use_context, RwSignal, Scope};
use floem::{
kurbo::Size,
reactive::{use_context, Memo, RwSignal, Scope},
};
use serde::{Deserialize, Serialize};
use super::{
@@ -34,6 +37,21 @@ pub fn default_panel_order() -> PanelOrder {
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,
@@ -49,6 +67,7 @@ pub struct PanelInfo {
pub panels: PanelOrder,
pub styles: im::HashMap<PanelPosition, PanelStyle>,
pub size: PanelSize,
pub sections: im::HashMap<PanelSection, bool>,
}
#[derive(Clone)]
@@ -56,14 +75,18 @@ pub struct PanelData {
pub panels: RwSignal<PanelOrder>,
pub styles: RwSignal<im::HashMap<PanelPosition, PanelStyle>>,
pub size: RwSignal<PanelSize>,
pub common: CommonData,
pub available_size: Memo<Size>,
pub sections: RwSignal<im::HashMap<PanelSection, RwSignal<bool>>>,
pub common: Rc<CommonData>,
}
impl PanelData {
pub fn new(
cx: Scope,
panels: im::HashMap<PanelPosition, im::Vector<PanelKind>>,
common: CommonData,
available_size: Memo<Size>,
sections: im::HashMap<PanelSection, bool>,
common: Rc<CommonData>,
) -> Self {
let panels = cx.create_rw_signal(panels);
@@ -125,11 +148,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,
}
}
@@ -139,6 +170,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(),
}
}
@@ -383,6 +420,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(&section).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(

Some files were not shown because too many files have changed in this diff Show More