diff --git a/.github/workflows/aur.yml b/.github/workflows/aur.yml index f39c51d..ce9f35c 100644 --- a/.github/workflows/aur.yml +++ b/.github/workflows/aur.yml @@ -12,6 +12,7 @@ on: jobs: aur-publish: runs-on: ubuntu-latest + environment: aur if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Fetch Repository @@ -29,9 +30,9 @@ jobs: sha_x86_64=$(cut -d' ' -f1 <(cat x86_64.txt)) sha_aarch64=$(cut -d' ' -f1 <(cat aarch64.txt)) + sed -i "s/^pkgver=.*/pkgver=$GITHUB_REF_NAME/g" ./aur/PKGBUILD sed -i "s/^sha256sums_x86_64=.*/sha256sums_x86_64=\(\'$sha_x86_64\'\)/g" ./aur/PKGBUILD sed -i "s/^sha256sums_aarch64=.*/sha256sums_aarch64=\(\'$sha_aarch64\'\)/g" ./aur/PKGBUILD - sed -i "s/^pkgver=.*/pkgver=$GITHUB_REF_NAME/g" ./aur/PKGBUILD - name: Publish AUR lcode uses: KSXGitHub/github-actions-deploy-aur@v2 diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 18448ab..cd89da9 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -1,6 +1,7 @@ name: Clippy -on: [push, pull_request, merge_group] +on: + push: env: RUST_BACKTRACE: 1 @@ -25,6 +26,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-clippy-${{ hashFiles('**/Cargo.lock') }} - name: Install Dbus @@ -39,5 +41,10 @@ jobs: toolchain: nightly components: clippy - - name: Run clippy - run: cargo clippy + - name: Run Clippy Allow Warning + if: ${{ ! (github.ref == 'refs/heads/main' || github.event_name == 'pull_request') }} + run: cargo clippy --locked + + - name: Run Clippy Deny Warning + if: github.ref == 'refs/heads/main' || github.event_name == 'pull_request' + run: cargo clippy --locked -- -D warnings diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml index 569cd17..a4511c0 100644 --- a/.github/workflows/fmt.yml +++ b/.github/workflows/fmt.yml @@ -21,6 +21,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-fmt-${{ hashFiles('**/Cargo.lock') }} - uses: dtolnay/rust-toolchain@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cdd2d64..8272dcd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-release-${{ hashFiles('**/Cargo.lock') }} - name: Install Nightly Rust Toolchain @@ -42,7 +43,7 @@ jobs: tool: cargo-binstall - name: Install cargo-release - run: cargo binstall -y cargo-release + run: cargo binstall -y --locked cargo-release - name: Install Dbus run: sudo apt update && sudo apt install libdbus-1-dev pkg-config @@ -84,6 +85,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Nightly Rust Toolchain @@ -175,6 +177,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - name: Install Dbus diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f3a68f..95faba7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,47 +1,15 @@ name: Test -on: [pull_request, merge_group] +on: + push: + branches: + - "main" + pull_request: env: RUST_BACKTRACE: 1 jobs: - clippy: - name: Clippy rust - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - rust: [nightly] - steps: - - uses: actions/checkout/@v4 - - - name: Cargo Cache - id: cargo-cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - key: ${{ runner.os }}-test-clippy-${{ hashFiles('**/Cargo.lock') }} - - - name: Install Dbus - if: startsWith(matrix.os, 'ubuntu-') - run: | - sudo apt update - sudo apt install -y libdbus-1-dev pkg-config - - - name: Install Toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly - components: clippy - - - name: Run clippy - run: cargo clippy -- -D warnings - test: name: Test runs-on: ${{ matrix.os }} @@ -64,6 +32,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} - name: Windows Cache leetcode @@ -77,6 +46,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} - name: Macos Cache leetcode @@ -90,6 +60,7 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} - name: Rust Toolchain @@ -111,4 +82,4 @@ jobs: - name: Run Test run: | # cargo nextest run get_all_pbs_works new_get_index # generate database - cargo nextest run + cargo nextest run --locked diff --git a/CHANGELOG.md b/CHANGELOG.md index 3701732..2fcbd10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Perf + +- avoid unnecessary render and filter. + ## [0.9.1] - 2024-05-18 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 37a6a75..c3de0aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,7 +296,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -336,7 +336,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -353,7 +353,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -633,7 +633,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -862,7 +862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1075,7 +1075,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1295,7 +1295,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1513,7 +1513,7 @@ dependencies = [ "markup5ever 0.12.1", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1656,12 +1656,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "indoc" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" - [[package]] name = "inherent" version = "1.0.11" @@ -1670,7 +1664,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -1810,7 +1804,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lcode" -version = "0.9.1" +version = "0.9.2" dependencies = [ "atoi", "clap", @@ -1838,7 +1832,7 @@ dependencies = [ [[package]] name = "lcode-config" -version = "0.3.12" +version = "0.3.13" dependencies = [ "crossterm 0.27.0", "decrypt-cookies", @@ -1854,7 +1848,7 @@ dependencies = [ [[package]] name = "leetcode-api" -version = "0.3.16" +version = "0.4.0" dependencies = [ "colored", "decrypt-cookies", @@ -2070,7 +2064,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2371,7 +2365,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2524,7 +2518,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2562,7 +2556,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -2758,21 +2752,21 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" dependencies = [ "bitflags 2.5.0", "cassowary", "compact_str", "crossterm 0.27.0", - "indoc", "itertools", "lru", "paste", "stability", "strum 0.26.2", "unicode-segmentation", + "unicode-truncate", "unicode-width", ] @@ -3088,7 +3082,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3123,7 +3117,7 @@ dependencies = [ "proc-macro2", "quote", "sea-bae", - "syn 2.0.65", + "syn 2.0.66", "unicode-ident", ] @@ -3226,7 +3220,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3248,7 +3242,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3533,12 +3527,12 @@ dependencies = [ [[package]] name = "stability" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -3616,7 +3610,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3659,9 +3653,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.65" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -3770,7 +3764,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -3866,7 +3860,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -4009,7 +4003,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -4130,6 +4124,16 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +dependencies = [ + "itertools", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.12" @@ -4247,7 +4251,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -4281,7 +4285,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4398,7 +4402,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -4409,7 +4413,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] @@ -4691,7 +4695,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.66", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dfac831..268aece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,14 +46,14 @@ html2text = { version = "^0" } pretty_assertions = { version = "^1" } paste = { version = "^1" } -ratatui = { version = "=0.26.1" } +ratatui = { version = "^0.26.3" } colored = { version = "^2" } key_parse = { path = "./crates/key_parse", version = "^0.2" } decrypt-cookies = { version = "^0.5" } lcode-config = { path = "./crates/lcode-config", version = "^0.3" } -leetcode-api = { path = "./crates/leetcode-api", version = "^0.3", features = ["ratatui"] } +leetcode-api = { path = "./crates/leetcode-api", version = "^0.4", features = ["ratatui"] } [workspace.lints.rust] temporary_cstring_as_ptr = "deny" diff --git a/README-CN.md b/README-CN.md index 252b5ba..3906de5 100644 --- a/README-CN.md +++ b/README-CN.md @@ -322,4 +322,4 @@ cargo_integr = true ## 用户信息 -你可以查看 tui 的 infos/tab3 界面来确认 cookies 是有效的. +你可以查看 tui 的 info/tab3 界面来确认 cookies 是有效的. diff --git a/README.md b/README.md index 9367747..4ef9843 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ cargo_integr = true ## 👣The User Info -You can checkout the infos/tab3 in tui for ensure cookies is valid. +You can checkout the info/tab3 in tui for ensure cookies is valid. ## Todo diff --git a/aur/PKGBUILD b/aur/PKGBUILD index 9b63d17..de1ca51 100644 --- a/aur/PKGBUILD +++ b/aur/PKGBUILD @@ -9,26 +9,16 @@ arch=('x86_64' 'aarch64') url="https://github.com/saying121/lcode" license=('Apache-2.0') depends=('dbus' 'sqlite' 'mdcat') -# makedepends=('jq' 'curl') checkdepends=() optdepends=('gnome-keyring') provides=('lcode') conflicts=() -replaces=() -groups=() -backup=() -options=() -install= source_x86_64=("https://github.com/saying121/${_repository}/releases/download/${pkgver}/${_binname}-${pkgver}-x86_64-unknown-linux-gnu.tar.gz") sha256sums_x86_64=('') source_aarch64=("https://github.com/saying121/${_repository}/releases/download/${pkgver}/${_binname}-${pkgver}-aarch64-unknown-linux-gnu.tar.gz") sha256sums_aarch64=('') -# pkgver() { -# curl -s "https://api.github.com/repos/saying121/lcode/releases/latest" | jq '.tag_name' -# } - build() { install -dm755 "completions" ./lcode --generate=zsh >completions/_${_binname} diff --git a/crates/lcode-config/Cargo.toml b/crates/lcode-config/Cargo.toml index 10f6c82..f946e5e 100644 --- a/crates/lcode-config/Cargo.toml +++ b/crates/lcode-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lcode-config" -version = "0.3.12" +version = "0.3.13" license = "MIT OR Apache-2.0" description = "config mod for [lcode](https://crates.io/crates/lcode)" edition = { workspace = true } diff --git a/crates/lcode/Cargo.toml b/crates/lcode/Cargo.toml index 9540cff..7ec8c98 100644 --- a/crates/lcode/Cargo.toml +++ b/crates/lcode/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lcode" -version = "0.9.1" +version = "0.9.2" description = "An application of terminal write leetcode.一个终端刷力扣的应用" documentation = "https://docs.rs/lcode" license = "Apache-2.0" diff --git a/crates/lcode/src/app/dispatch/handle_key.rs b/crates/lcode/src/app/dispatch/handle_key.rs index e33e4d8..6ec6248 100644 --- a/crates/lcode/src/app/dispatch/handle_key.rs +++ b/crates/lcode/src/app/dispatch/handle_key.rs @@ -7,19 +7,19 @@ use crate::app::{inner::App, Tab2Panel, TuiIndex, TuiMode}; impl<'app_lf> App<'app_lf> { pub async fn handle_key(&mut self, keyevent: KeyEvent) { let temp = if matches!(self.tab_index, TuiIndex::Select) - && matches!(self.select.input_line_mode, TuiMode::Insert) + && matches!(self.select.inputline.mode, TuiMode::Insert) { self.select .keymap_insert(CrossEvent::Key(keyevent)) } else if matches!(self.tab_index, TuiIndex::Topic) - && matches!(self.topic.input_line_mode, TuiMode::Insert) + && matches!(self.topic.inputline.mode, TuiMode::Insert) { self.topic .keymap_insert(CrossEvent::Key(keyevent)) } else if matches!(self.tab_index, TuiIndex::Edit) { - match self.edit.code_block_mode { + match self.edit.code_block.mode { TuiMode::Normal => match keyevent.code { KeyCode::Char('s') if keyevent.modifiers == KeyModifiers::CONTROL => { self.save_code().await.ok(); @@ -76,7 +76,7 @@ impl<'app_lf> App<'app_lf> { /// do a action pub async fn do_action(&mut self, action: &str) -> Result<()> { let cond = match self.tab_index { - TuiIndex::Select if matches!(self.select.input_line_mode, TuiMode::OutEdit) => { + TuiIndex::Select if matches!(self.select.inputline.mode, TuiMode::OutEdit) => { match action { UP => self.select.prev_qs(), DOWN => self.select.next_qs(), @@ -89,7 +89,7 @@ impl<'app_lf> App<'app_lf> { _ => false, } }, - TuiIndex::Edit if matches!(self.edit.code_block_mode, TuiMode::OutEdit) => match action + TuiIndex::Edit if matches!(self.edit.code_block.mode, TuiMode::OutEdit) => match action { UP => self.edit.vertical_scroll_k(), DOWN => self.edit.vertical_scroll_j(), @@ -104,7 +104,7 @@ impl<'app_lf> App<'app_lf> { .await .is_ok(), - TOGGLE_CURSOR if self.edit.show_pop_menu => self.menu_button_trig(), + TOGGLE_CURSOR if self.edit.button.show => self.menu_button_trig(), TOGGLE_MENU => self.edit.toggle_menu(), TOGGLE_SUBMIT_RES => self.edit.toggle_submit_res(), @@ -117,7 +117,7 @@ impl<'app_lf> App<'app_lf> { }, _ => false, }, - TuiIndex::Topic if matches!(self.topic.input_line_mode, TuiMode::OutEdit) => { + TuiIndex::Topic if matches!(self.topic.inputline.mode, TuiMode::OutEdit) => { match action { UP => self.topic.up(), DOWN => self.topic.down(), @@ -145,12 +145,12 @@ impl<'app_lf> App<'app_lf> { _ => false, } }, - TuiIndex::Infos => match action { - UP => self.infos.prev_item(), - DOWN => self.infos.next_item(), - TOP => self.infos.first_item(), - BOTTOM => self.infos.last_item(), - TOGGLE_CURSOR => self.infos.trigger(), + TuiIndex::Info => match action { + UP => self.info.prev_item(), + DOWN => self.info.next_item(), + TOP => self.info.first_item(), + BOTTOM => self.info.last_item(), + TOGGLE_CURSOR => self.info.trigger(), _ => false, }, _ => false, diff --git a/crates/lcode/src/app/edit.rs b/crates/lcode/src/app/edit.rs deleted file mode 100644 index 6e12685..0000000 --- a/crates/lcode/src/app/edit.rs +++ /dev/null @@ -1,464 +0,0 @@ -use crossterm::event::{self, Event as CrossEvent, KeyCode}; -use leetcode_api::leetcode::resps::run_res::RunResult; -use ratatui::widgets::ScrollbarState; -use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea}; - -use super::TuiMode; -use crate::mytui::my_widget::botton::{ButtonState, ButtonStates}; - -// tab1 edit -#[derive(Clone)] -#[derive(Debug)] -#[derive(Default)] -pub struct EditCode<'tab1> { - pub code_block: TextArea<'tab1>, - pub code_block_mode: TuiMode, - - pub vertical_row_len: usize, - pub content_vert_scroll_state: ScrollbarState, - pub content_vert_scroll: usize, - pub horizontal_col_len: usize, - pub horizontal_scroll_state: ScrollbarState, - pub horizontal_scroll: usize, - - // test and submit - pub submitting: bool, - pub show_pop_menu: bool, - - pub button_state: ButtonStates, - pub select_button: usize, - - pub submit_res: RunResult, - pub show_submit_res: bool, - pub submit_vert_scroll_state: ScrollbarState, - pub submit_vert_scroll: usize, - pub submit_hori_scroll_state: ScrollbarState, - pub submit_hori_scroll: usize, - pub submit_row_len: usize, - - pub test_res: RunResult, - pub show_test_res: bool, - pub test_vert_scroll_state: ScrollbarState, - pub test_vert_scroll: usize, - pub test_hori_scroll_state: ScrollbarState, - pub test_hori_scroll: usize, - pub test_row_len: usize, -} - -impl<'tab1> EditCode<'tab1> { - pub fn normal_map(&mut self, event: CrossEvent) -> bool { - match event.into() { - // Mappings in normal mode - Input { - key: Key::Char('d'), ctrl: false, .. - } => match event::read() { - Ok(CrossEvent::Key(keyevent)) => match keyevent.code { - KeyCode::Char('d') => { - self.code_block - .move_cursor(CursorMove::Head); - self.code_block.delete_line_by_end(); - self.code_block.delete_next_char() - }, - KeyCode::Char('w') => self.code_block.delete_next_word(), - _ => false, - }, - _ => false, - }, - Input { key: Key::Char('g'), .. } => match event::read() { - Ok(CrossEvent::Key(key)) => { - if key.code == KeyCode::Char('g') { - self.code_block - .move_cursor(CursorMove::Top); - true - } - else { - false - } - }, - _ => false, - }, - Input { key: Key::Char('G'), .. } => { - self.code_block - .move_cursor(CursorMove::Bottom); - true - }, - Input { key: Key::Char('h'), .. } => { - self.code_block - .move_cursor(CursorMove::Back); - true - }, - Input { key: Key::Char('j'), .. } => { - self.code_block - .move_cursor(CursorMove::Down); - true - }, - Input { key: Key::Char('k'), .. } => { - self.code_block - .move_cursor(CursorMove::Up); - true - }, - Input { key: Key::Char('l'), .. } => { - self.code_block - .move_cursor(CursorMove::Forward); - true - }, - Input { key: Key::Char('w'), .. } => { - self.code_block - .move_cursor(CursorMove::WordForward); - true - }, - Input { - key: Key::Char('b'), ctrl: false, .. - } => { - self.code_block - .move_cursor(CursorMove::WordBack); - true - }, - Input { key: Key::Char('^' | '0'), .. } => { - self.code_block - .move_cursor(CursorMove::Head); - true - }, - Input { key: Key::Char('$'), .. } => { - self.code_block - .move_cursor(CursorMove::End); - true - }, - Input { key: Key::Char('D'), .. } => self.code_block.delete_line_by_end(), - Input { key: Key::Char('C'), .. } => { - self.code_block.delete_line_by_end(); - self.be_code_insert() - }, - Input { key: Key::Char('p'), .. } => self.code_block.paste(), - Input { - key: Key::Char('u'), ctrl: false, .. - } => self.code_block.undo(), - Input { key: Key::Char('r'), ctrl: true, .. } => self.code_block.redo(), - Input { key: Key::Char('x'), .. } => self.code_block.delete_next_char(), - Input { key: Key::Char('i'), .. } => self.be_code_insert(), - Input { key: Key::Char('a'), .. } => { - self.code_block - .move_cursor(CursorMove::Forward); - self.be_code_insert() - }, - Input { key: Key::Char('A'), .. } => { - self.code_block - .move_cursor(CursorMove::End); - self.be_code_insert() - }, - Input { key: Key::Char('o'), .. } => { - self.code_block - .move_cursor(CursorMove::End); - self.code_block.insert_newline(); - self.be_code_insert() - }, - Input { key: Key::Char('O'), .. } => { - self.code_block - .move_cursor(CursorMove::Head); - self.code_block.insert_newline(); - self.code_block - .move_cursor(CursorMove::Up); - self.be_code_insert() - }, - Input { key: Key::Char('I'), .. } => { - self.code_block - .move_cursor(CursorMove::Head); - self.be_code_insert() - }, - Input { key: Key::Char('e'), ctrl: true, .. } => { - self.code_block.scroll((1, 0)); - true - }, - Input { key: Key::Char('y'), ctrl: true, .. } => { - self.code_block.scroll((-1, 0)); - true - }, - Input { key: Key::Char('d'), ctrl: true, .. } => { - self.code_block - .scroll(Scrolling::HalfPageDown); - true - }, - Input { key: Key::Char('u'), ctrl: true, .. } => { - self.code_block - .scroll(Scrolling::HalfPageUp); - true - }, - Input { key: Key::Char('f'), ctrl: true, .. } => { - self.code_block - .scroll(Scrolling::PageDown); - true - }, - Input { key: Key::Char('b'), ctrl: true, .. } => { - self.code_block - .scroll(Scrolling::PageUp); - true - }, - - Input { key: Key::Char('q'), .. } => self.quit_edit_tui(), - _ => false, - } - } - - pub fn insert_keymap(&mut self, event: CrossEvent) -> bool { - match event.into() { - Input { key: Key::Esc, .. } => self.be_code_normal(), - input => self.code_block.input(input), // Use default key mappings in insert mode(emacs) - } - } - pub fn quit_edit_tui(&mut self) -> bool { - self.code_block_mode = TuiMode::OutEdit; - true - } - pub fn be_code_insert(&mut self) -> bool { - self.code_block_mode = TuiMode::Insert; - true - } - - pub fn be_code_normal(&mut self) -> bool { - self.code_block_mode = TuiMode::Normal; - true - } - pub fn start_edit_tui(&mut self) -> bool { - self.code_block_mode = TuiMode::Normal; - true - } -} - -// Show only one pop view every time. -impl<'tab1> EditCode<'tab1> { - pub fn toggle_menu(&mut self) -> bool { - self.show_pop_menu = !self.show_pop_menu; - self.show_test_res = false; - self.show_submit_res = false; - true - } - pub fn toggle_test_res(&mut self) -> bool { - self.show_test_res = !self.show_test_res; - self.show_pop_menu = false; - self.show_submit_res = false; - true - } - pub fn toggle_submit_res(&mut self) -> bool { - self.show_submit_res = !self.show_submit_res; - self.show_test_res = false; - self.show_pop_menu = false; - true - } -} - -impl<'tab1> EditCode<'tab1> { - pub fn close_pop(&mut self) -> bool { - if self.show_test_res { - self.show_test_res = false; - } - else if self.show_submit_res { - self.show_submit_res = false; - } - else if self.show_pop_menu { - self.show_pop_menu = false; - } - true - } - - pub fn vertical_scroll_j(&mut self) -> bool { - if self.show_test_res { - if self.test_vert_scroll < self.test_row_len.saturating_sub(4) { - self.test_vert_scroll = self.test_vert_scroll.saturating_add(1); - self.test_vert_scroll_state = self - .test_vert_scroll_state - .position(self.test_vert_scroll); - } - } - else if self.show_submit_res { - if self.submit_vert_scroll < self.submit_row_len.saturating_sub(4) { - self.submit_vert_scroll = self - .submit_vert_scroll - .saturating_add(1); - self.submit_vert_scroll_state = self - .submit_vert_scroll_state - .position(self.submit_vert_scroll); - } - } - else if !self.show_pop_menu - && self.content_vert_scroll < self.vertical_row_len.saturating_sub(4) - { - self.content_vert_scroll = self - .content_vert_scroll - .saturating_add(1); - self.content_vert_scroll_state = self - .content_vert_scroll_state - .position(self.content_vert_scroll); - } - true - } - - pub fn vertical_scroll_k(&mut self) -> bool { - if self.show_test_res { - self.test_vert_scroll = self.test_vert_scroll.saturating_sub(1); - self.test_vert_scroll_state = self - .test_vert_scroll_state - .position(self.test_vert_scroll); - } - else if self.show_submit_res { - self.submit_vert_scroll = self - .submit_vert_scroll - .saturating_sub(1); - self.submit_vert_scroll_state = self - .submit_vert_scroll_state - .position(self.submit_vert_scroll); - } - else if !self.show_pop_menu { - self.content_vert_scroll = self - .content_vert_scroll - .saturating_sub(1); - self.content_vert_scroll_state = self - .content_vert_scroll_state - .position(self.content_vert_scroll); - } - true - } - - pub fn horizontal_scroll_h(&mut self) -> bool { - if self.show_test_res { - self.test_hori_scroll = self.test_hori_scroll.saturating_sub(2); - self.test_hori_scroll_state = self - .test_hori_scroll_state - .position(self.test_hori_scroll); - } - else if self.show_submit_res { - self.submit_hori_scroll = self - .submit_hori_scroll - .saturating_sub(2); - self.submit_hori_scroll_state = self - .submit_hori_scroll_state - .position(self.submit_hori_scroll); - } - else if self.show_pop_menu { - if self.button_state.states[self.select_button] != ButtonState::Active { - self.button_state.states[self.select_button] = ButtonState::Normal; - } - self.select_button = self.select_button.saturating_sub(1); - if self.button_state.states[self.select_button] != ButtonState::Active { - self.button_state.states[self.select_button] = ButtonState::Selected; - } - } - else { - self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1); - self.horizontal_scroll_state = self - .horizontal_scroll_state - .position(self.horizontal_scroll); - } - true - } - - pub fn horizontal_scroll_l(&mut self) -> bool { - if self.show_test_res { - self.test_hori_scroll = self.test_hori_scroll.saturating_add(2); - self.test_hori_scroll_state = self - .test_hori_scroll_state - .position(self.test_hori_scroll); - } - else if self.show_submit_res { - self.submit_hori_scroll = self - .submit_hori_scroll - .saturating_add(2); - self.submit_hori_scroll_state = self - .submit_hori_scroll_state - .position(self.submit_hori_scroll); - } - else if self.show_pop_menu { - if self.button_state.states[self.select_button] != ButtonState::Active { - self.button_state.states[self.select_button] = ButtonState::Normal; - } - - self.select_button = self - .select_button - .saturating_add(1) - .min(1); - - if self.button_state.states[self.select_button] != ButtonState::Active { - self.button_state.states[self.select_button] = ButtonState::Selected; - } - } - else { - if self.horizontal_scroll - < self - .horizontal_col_len - .saturating_sub(4) - { - self.horizontal_scroll = self.horizontal_scroll.saturating_add(1); - } - self.horizontal_scroll_state = self - .horizontal_scroll_state - .position(self.horizontal_scroll); - } - true - } - - pub fn vertical_scroll_gg(&mut self) -> bool { - if self.show_submit_res { - self.submit_vert_scroll = 0; - self.submit_vert_scroll_state = self - .submit_vert_scroll_state - .position(self.submit_vert_scroll); - } - else if self.show_test_res { - self.test_vert_scroll = 0; - self.test_vert_scroll_state = self - .test_vert_scroll_state - .position(self.test_vert_scroll); - } - else { - self.content_vert_scroll = 0; - self.content_vert_scroll_state = self - .content_vert_scroll_state - .position(self.content_vert_scroll); - } - true - } - - #[allow(non_snake_case)] - pub fn vertical_scroll_G(&mut self) -> bool { - if self.show_submit_res { - self.submit_vert_scroll = self.submit_row_len.saturating_sub(4); - self.submit_vert_scroll_state = self - .submit_vert_scroll_state - .position(self.submit_vert_scroll); - } - else if self.show_test_res { - self.test_vert_scroll = self.test_row_len.saturating_sub(4); - self.test_vert_scroll_state = self - .test_vert_scroll_state - .position(self.test_vert_scroll); - } - else { - self.content_vert_scroll = self.vertical_row_len.saturating_sub(4); - self.content_vert_scroll_state = self - .content_vert_scroll_state - .position(self.content_vert_scroll); - } - true - } - fn submit_res_view_head(&mut self) { - self.submit_hori_scroll = 0; - self.submit_hori_scroll_state = self - .submit_hori_scroll_state - .position(self.submit_hori_scroll); - } - fn test_res_view_head(&mut self) { - self.test_hori_scroll = 0; - self.test_hori_scroll_state = self - .test_hori_scroll_state - .position(self.test_hori_scroll); - } - /// goto first column - pub fn goto_pop_head(&mut self) -> bool { - if self.show_submit_res { - self.submit_res_view_head(); - } - if self.show_test_res { - self.test_res_view_head(); - } - true - } -} diff --git a/crates/lcode/src/app/edit/cmds/button.rs b/crates/lcode/src/app/edit/cmds/button.rs new file mode 100644 index 0000000..adcd79b --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/button.rs @@ -0,0 +1,67 @@ +use crate::mytui::my_widget::botton::{ButtonState, ButtonStates}; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct ButState { + pub button_state: ButtonStates, + pub select_button: usize, + pub show: bool, + pub submitting: bool, +} + +impl ButState { + pub fn active_but(&mut self) { + self.button_state.states[self.select_button] = ButtonState::Active; + } + pub fn done(&mut self) { + self.submitting = false; + } + pub fn start(&mut self) { + self.submitting = false; + } + pub fn test_done(&mut self) { + self.done(); + self.button_state.states[0] = ButtonState::Normal; + } + pub fn submit_done(&mut self) { + self.done(); + self.button_state.states[1] = ButtonState::Normal; + } +} + +impl ButState { + #[allow(dead_code)] + pub fn open(&mut self) { + self.show = true; + } + pub fn close(&mut self) { + self.show = false; + } + pub fn toggle(&mut self) { + self.show = !self.show; + } + pub fn left(&mut self) { + if self.button_state.states[self.select_button] != ButtonState::Active { + self.button_state.states[self.select_button] = ButtonState::Normal; + } + self.select_button = self.select_button.saturating_sub(1); + if self.button_state.states[self.select_button] != ButtonState::Active { + self.button_state.states[self.select_button] = ButtonState::Selected; + } + } + pub fn right(&mut self) { + if self.button_state.states[self.select_button] != ButtonState::Active { + self.button_state.states[self.select_button] = ButtonState::Normal; + } + + self.select_button = self + .select_button + .saturating_add(1) + .min(1); + + if self.button_state.states[self.select_button] != ButtonState::Active { + self.button_state.states[self.select_button] = ButtonState::Selected; + } + } +} diff --git a/crates/lcode/src/app/edit/cmds/codeblock.rs b/crates/lcode/src/app/edit/cmds/codeblock.rs new file mode 100644 index 0000000..f130f3f --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/codeblock.rs @@ -0,0 +1,186 @@ +use crossterm::event::{self, Event as CrossEvent, KeyCode}; +use tui_textarea::{CursorMove, Input, Key, Scrolling, TextArea}; + +use crate::app::TuiMode; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct CodeBlock<'block> { + pub code_block: TextArea<'block>, + pub mode: TuiMode, +} + +impl<'block> CodeBlock<'block> { + pub fn quit_edit_tui(&mut self) -> bool { + self.mode = TuiMode::OutEdit; + true + } + pub fn be_code_insert(&mut self) -> bool { + self.mode = TuiMode::Insert; + true + } + pub fn be_code_normal(&mut self) -> bool { + self.mode = TuiMode::Normal; + true + } + pub fn start_edit_tui(&mut self) -> bool { + self.mode = TuiMode::Normal; + true + } + pub fn insert_map(&mut self, input: Input) -> bool { + self.code_block.input(input) + } + pub fn normal_map(&mut self, event: CrossEvent) -> bool { + match event.into() { + // Mappings in normal mode + Input { + key: Key::Char('d'), ctrl: false, .. + } => match event::read() { + Ok(CrossEvent::Key(keyevent)) => match keyevent.code { + KeyCode::Char('d') => { + self.code_block + .move_cursor(CursorMove::Head); + self.code_block.delete_line_by_end(); + self.code_block.delete_next_char() + }, + KeyCode::Char('w') => self.code_block.delete_next_word(), + _ => false, + }, + _ => false, + }, + Input { key: Key::Char('g'), .. } => match event::read() { + Ok(CrossEvent::Key(key)) => { + if key.code == KeyCode::Char('g') { + self.code_block + .move_cursor(CursorMove::Top); + true + } + else { + false + } + }, + _ => false, + }, + Input { key: Key::Char('G'), .. } => { + self.code_block + .move_cursor(CursorMove::Bottom); + true + }, + Input { key: Key::Char('h'), .. } => { + self.code_block + .move_cursor(CursorMove::Back); + true + }, + Input { key: Key::Char('j'), .. } => { + self.code_block + .move_cursor(CursorMove::Down); + true + }, + Input { key: Key::Char('k'), .. } => { + self.code_block + .move_cursor(CursorMove::Up); + true + }, + Input { key: Key::Char('l'), .. } => { + self.code_block + .move_cursor(CursorMove::Forward); + true + }, + Input { key: Key::Char('w'), .. } => { + self.code_block + .move_cursor(CursorMove::WordForward); + true + }, + Input { + key: Key::Char('b'), ctrl: false, .. + } => { + self.code_block + .move_cursor(CursorMove::WordBack); + true + }, + Input { key: Key::Char('^' | '0'), .. } => { + self.code_block + .move_cursor(CursorMove::Head); + true + }, + Input { key: Key::Char('$'), .. } => { + self.code_block + .move_cursor(CursorMove::End); + true + }, + Input { key: Key::Char('D'), .. } => self.code_block.delete_line_by_end(), + Input { key: Key::Char('C'), .. } => { + self.code_block.delete_line_by_end(); + self.be_code_insert() + }, + Input { key: Key::Char('p'), .. } => self.code_block.paste(), + Input { + key: Key::Char('u'), ctrl: false, .. + } => self.code_block.undo(), + Input { key: Key::Char('r'), ctrl: true, .. } => self.code_block.redo(), + Input { key: Key::Char('x'), .. } => self.code_block.delete_next_char(), + Input { key: Key::Char('i'), .. } => self.be_code_insert(), + Input { key: Key::Char('a'), .. } => { + self.code_block + .move_cursor(CursorMove::Forward); + self.be_code_insert() + }, + Input { key: Key::Char('A'), .. } => { + self.code_block + .move_cursor(CursorMove::End); + self.be_code_insert() + }, + Input { key: Key::Char('o'), .. } => { + self.code_block + .move_cursor(CursorMove::End); + self.code_block.insert_newline(); + self.be_code_insert() + }, + Input { key: Key::Char('O'), .. } => { + self.code_block + .move_cursor(CursorMove::Head); + self.code_block.insert_newline(); + self.code_block + .move_cursor(CursorMove::Up); + self.be_code_insert() + }, + Input { key: Key::Char('I'), .. } => { + self.code_block + .move_cursor(CursorMove::Head); + self.be_code_insert() + }, + Input { key: Key::Char('e'), ctrl: true, .. } => { + self.code_block.scroll((1, 0)); + true + }, + Input { key: Key::Char('y'), ctrl: true, .. } => { + self.code_block.scroll((-1, 0)); + true + }, + Input { key: Key::Char('d'), ctrl: true, .. } => { + self.code_block + .scroll(Scrolling::HalfPageDown); + true + }, + Input { key: Key::Char('u'), ctrl: true, .. } => { + self.code_block + .scroll(Scrolling::HalfPageUp); + true + }, + Input { key: Key::Char('f'), ctrl: true, .. } => { + self.code_block + .scroll(Scrolling::PageDown); + true + }, + Input { key: Key::Char('b'), ctrl: true, .. } => { + self.code_block + .scroll(Scrolling::PageUp); + true + }, + + Input { key: Key::Char('q'), .. } => self.quit_edit_tui(), + _ => false, + } + } +} diff --git a/crates/lcode/src/app/edit/cmds/content.rs b/crates/lcode/src/app/edit/cmds/content.rs new file mode 100644 index 0000000..283a35d --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/content.rs @@ -0,0 +1,57 @@ +use ratatui::widgets::ScrollbarState; + +#[derive(Clone, Copy)] +#[derive(Debug)] +#[derive(Default)] +pub struct ContentState { + pub content_row_num: usize, + pub vert_scroll_state: ScrollbarState, + pub vert_scroll: usize, + pub column_len: usize, + pub horizontal_scroll_state: ScrollbarState, + pub horizontal_scroll: usize, +} + +impl ContentState { + pub fn top(&mut self) { + self.vert_scroll = 0; + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn bottom(&mut self) { + self.vert_scroll = self.content_row_num.saturating_sub(4); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + + pub fn up(&mut self) { + self.vert_scroll = self.vert_scroll.saturating_sub(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn down(&mut self) { + if self.vert_scroll < self.content_row_num.saturating_sub(4) { + self.vert_scroll = self.vert_scroll.saturating_add(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + } + pub fn left(&mut self) { + self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1); + self.horizontal_scroll_state = self + .horizontal_scroll_state + .position(self.horizontal_scroll); + } + pub fn right(&mut self) { + if self.horizontal_scroll < self.column_len.saturating_sub(4) { + self.horizontal_scroll = self.horizontal_scroll.saturating_add(1); + } + self.horizontal_scroll_state = self + .horizontal_scroll_state + .position(self.horizontal_scroll); + } +} diff --git a/crates/lcode/src/app/edit/cmds/mod.rs b/crates/lcode/src/app/edit/cmds/mod.rs new file mode 100644 index 0000000..d02b97b --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/mod.rs @@ -0,0 +1,5 @@ +pub mod button; +pub mod codeblock; +pub mod content; +pub mod submit; +pub mod test; diff --git a/crates/lcode/src/app/edit/cmds/submit.rs b/crates/lcode/src/app/edit/cmds/submit.rs new file mode 100644 index 0000000..03f1e5c --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/submit.rs @@ -0,0 +1,73 @@ +use leetcode_api::leetcode::resps::run_res::RunResult; +use ratatui::widgets::ScrollbarState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct SubmitState { + pub result: RunResult, + pub show: bool, + pub vert_scroll_state: ScrollbarState, + pub vert_scroll: usize, + pub hori_scroll_state: ScrollbarState, + pub hori_scroll: usize, + pub row_len: usize, +} + +impl SubmitState { + pub fn toggle(&mut self) { + self.show = !self.show; + } + pub fn close(&mut self) { + self.show = false; + } + pub fn open(&mut self) { + self.show = true; + } + + pub fn first(&mut self) { + self.vert_scroll = 0; + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn last(&mut self) { + self.vert_scroll = self.row_len.saturating_sub(4); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn goto_head(&mut self) { + self.hori_scroll = 0; + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } + + pub fn up(&mut self) { + self.vert_scroll = self.vert_scroll.saturating_sub(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn down(&mut self) { + if self.vert_scroll < self.row_len.saturating_sub(4) { + self.vert_scroll = self.vert_scroll.saturating_add(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + } + pub fn left(&mut self) { + self.hori_scroll = self.hori_scroll.saturating_sub(2); + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } + pub fn right(&mut self) { + self.hori_scroll = self.hori_scroll.saturating_add(2); + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } +} diff --git a/crates/lcode/src/app/edit/cmds/test.rs b/crates/lcode/src/app/edit/cmds/test.rs new file mode 100644 index 0000000..b869af1 --- /dev/null +++ b/crates/lcode/src/app/edit/cmds/test.rs @@ -0,0 +1,72 @@ +use leetcode_api::leetcode::resps::run_res::RunResult; +use ratatui::widgets::ScrollbarState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct TestState { + pub result: RunResult, + pub show: bool, + pub vert_scroll_state: ScrollbarState, + pub vert_scroll: usize, + pub hori_scroll_state: ScrollbarState, + pub hori_scroll: usize, + pub row_len: usize, +} + +impl TestState { + pub fn toggle(&mut self) { + self.show = !self.show; + } + pub fn close(&mut self) { + self.show = false; + } + pub fn open(&mut self) { + self.show = true; + } + pub fn first(&mut self) { + self.vert_scroll = 0; + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn last(&mut self) { + self.vert_scroll = self.row_len.saturating_sub(4); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn goto_head(&mut self) { + self.hori_scroll = 0; + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } + + pub fn up(&mut self) { + self.vert_scroll = self.vert_scroll.saturating_sub(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + pub fn down(&mut self) { + if self.vert_scroll < self.row_len.saturating_sub(4) { + self.vert_scroll = self.vert_scroll.saturating_add(1); + self.vert_scroll_state = self + .vert_scroll_state + .position(self.vert_scroll); + } + } + pub fn left(&mut self) { + self.hori_scroll = self.hori_scroll.saturating_sub(2); + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } + pub fn right(&mut self) { + self.hori_scroll = self.hori_scroll.saturating_add(2); + self.hori_scroll_state = self + .hori_scroll_state + .position(self.hori_scroll); + } +} diff --git a/crates/lcode/src/app/edit/mod.rs b/crates/lcode/src/app/edit/mod.rs new file mode 100644 index 0000000..fb3d6ae --- /dev/null +++ b/crates/lcode/src/app/edit/mod.rs @@ -0,0 +1,170 @@ +mod cmds; + +use crossterm::event::Event as CrossEvent; +use tui_textarea::{Input, Key}; + +use self::cmds::{button, codeblock, content, submit, test}; + +// tab1 edit +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct EditCode<'tab1> { + pub code_block: codeblock::CodeBlock<'tab1>, + pub qs_content: content::ContentState, + pub button: button::ButState, + pub submit: submit::SubmitState, + pub test: test::TestState, +} + +impl<'tab1> EditCode<'tab1> { + pub fn normal_map(&mut self, event: CrossEvent) -> bool { + self.code_block.normal_map(event) + } + + pub fn insert_keymap(&mut self, event: CrossEvent) -> bool { + match event.into() { + Input { key: Key::Esc, .. } => self.be_code_normal(), + input => self.code_block.insert_map(input), /* Use default key mappings in insert mode(emacs) */ + } + } + + pub fn be_code_normal(&mut self) -> bool { + self.code_block.be_code_normal() + } + pub fn start_edit_tui(&mut self) -> bool { + self.code_block.start_edit_tui() + } +} + +// Show only one pop view every time. +impl<'tab1> EditCode<'tab1> { + pub fn toggle_menu(&mut self) -> bool { + self.button.toggle(); + self.test.close(); + self.submit.close(); + true + } + pub fn toggle_test_res(&mut self) -> bool { + self.test.toggle(); + self.button.close(); + self.submit.close(); + true + } + pub fn toggle_submit_res(&mut self) -> bool { + self.submit.toggle(); + self.test.close(); + self.button.close(); + true + } +} + +impl<'tab1> EditCode<'tab1> { + pub fn close_pop(&mut self) -> bool { + if self.test.show { + self.test.close(); + } + else if self.submit.show { + self.submit.close(); + } + else if self.button.show { + self.button.close(); + } + true + } + + pub fn vertical_scroll_j(&mut self) -> bool { + if self.test.show { + self.test.down(); + } + else if self.submit.show { + self.submit.down(); + } + else if !self.button.show { + self.qs_content.down(); + } + true + } + + pub fn vertical_scroll_k(&mut self) -> bool { + if self.test.show { + self.test.up(); + } + else if self.submit.show { + self.submit.up(); + } + else if !self.button.show { + self.qs_content.up(); + } + true + } + + pub fn horizontal_scroll_h(&mut self) -> bool { + if self.test.show { + self.test.left(); + } + else if self.submit.show { + self.submit.left(); + } + else if self.button.show { + self.button.left(); + } + else { + self.qs_content.left(); + } + true + } + + pub fn horizontal_scroll_l(&mut self) -> bool { + if self.test.show { + self.test.right(); + } + else if self.submit.show { + self.submit.right(); + } + else if self.button.show { + self.button.right(); + } + else { + self.qs_content.right(); + } + true + } + + pub fn vertical_scroll_gg(&mut self) -> bool { + if self.submit.show { + self.submit.first(); + } + else if self.test.show { + self.test.first(); + } + else { + self.qs_content.top(); + } + true + } + + #[allow(non_snake_case)] + pub fn vertical_scroll_G(&mut self) -> bool { + if self.submit.show { + self.submit.last(); + } + else if self.test.show { + self.test.last(); + } + else { + self.qs_content.bottom(); + } + true + } + /// goto first column + pub fn goto_pop_head(&mut self) -> bool { + if self.submit.show { + self.submit.goto_head(); + } + if self.test.show { + self.test.goto_head(); + } + true + } +} diff --git a/crates/lcode/src/app/impl_app/edit_qs.rs b/crates/lcode/src/app/impl_app/edit_qs.rs index 74f1432..161dafc 100644 --- a/crates/lcode/src/app/impl_app/edit_qs.rs +++ b/crates/lcode/src/app/impl_app/edit_qs.rs @@ -4,10 +4,7 @@ use leetcode_api::{ }; use tracing::error; -use crate::{ - app::inner::App, - mytui::{my_widget::botton::ButtonState, myevent::UserEvent}, -}; +use crate::{app::inner::App, mytui::myevent::UserEvent}; impl<'app_lf> App<'app_lf> { pub fn get_qs_detail(&self, idslug: IdSlug, force: bool) { @@ -32,15 +29,10 @@ impl<'app_lf> App<'app_lf> { self.render(); } pub fn menu_button_trig(&mut self) -> bool { - match self.edit.select_button { - 0 => { - self.edit.button_state.states[0] = ButtonState::Active; - self.test_code() - }, - 1 => { - self.edit.button_state.states[1] = ButtonState::Active; - self.submit_code() - }, + self.edit.button.active_but(); + match self.edit.button.select_button { + 0 => self.test_code(), + 1 => self.submit_code(), _ => false, } } @@ -52,11 +44,11 @@ impl<'app_lf> App<'app_lf> { .unwrap_or_default(); // avoid repeated requests - if self.edit.submitting { + if self.edit.button.submitting { return false; } - self.edit.submitting = true; + self.edit.button.done(); let eve_tx = self.events.tx.clone(); tokio::spawn(async move { // min id is 1 @@ -93,10 +85,10 @@ impl<'app_lf> App<'app_lf> { .unwrap_or_default(); // avoid repeated requests - if self.edit.submitting { + if self.edit.button.submitting { return false; } - self.edit.submitting = true; + self.edit.button.start(); let eve_tx = self.events.tx.clone(); tokio::spawn(async move { @@ -123,25 +115,23 @@ impl<'app_lf> App<'app_lf> { false } pub fn test_done(&mut self, res: RunResult) { - self.edit.test_res = res; + self.edit.test.result = res; - self.edit.show_test_res = true; - self.edit.show_submit_res = false; - self.edit.show_pop_menu = false; + self.edit.test.open(); + self.edit.submit.close(); + self.edit.button.close(); - self.edit.submitting = false; - self.edit.button_state.states[0] = ButtonState::Normal; + self.edit.button.test_done(); self.render(); } pub fn submit_done(&mut self, res: RunResult) { - self.edit.submit_res = res; + self.edit.submit.result = res; - self.edit.show_submit_res = true; - self.edit.show_test_res = false; - self.edit.show_pop_menu = false; + self.edit.submit.open(); + self.edit.test.close(); + self.edit.button.close(); - self.edit.submitting = false; - self.edit.button_state.states[1] = ButtonState::Normal; + self.edit.button.submit_done(); self.render(); } } diff --git a/crates/lcode/src/app/impl_app/get_index.rs b/crates/lcode/src/app/impl_app/get_index.rs index ae8dd5a..ac435f7 100644 --- a/crates/lcode/src/app/impl_app/get_index.rs +++ b/crates/lcode/src/app/impl_app/get_index.rs @@ -16,10 +16,10 @@ use crate::{ impl<'app_lf> App<'app_lf> { pub fn sync_index(&mut self) -> bool { - if self.select.sync_state { + if self.select.sync_bar.show { return false; } - self.select.sync_state = true; + self.select.sync_bar.show = true; let eve_tx = self.events.tx.clone(); let handle = tokio::spawn(async move { @@ -54,21 +54,21 @@ impl<'app_lf> App<'app_lf> { } /// refresh `all_questions`, `filtered_qs` pub async fn sync_done(&mut self) { - self.select.sync_state = false; + self.select.sync_bar.close(); let questions = Query::query_all_index() .await .unwrap_or_default(); - self.select.all_questions = questions.into(); + self.select.qs_state.all_questions = questions.into(); self.select.filter_by_input(); self.render(); } pub fn sync_new(&mut self) -> bool { - if self.topic.sync_state { + if self.topic.sync_bar.show { return false; } - self.topic.sync_state = true; + self.topic.sync_bar.show = true; let eve_tx = self.events.tx.clone(); let handle = tokio::spawn(async move { if let Err(err) = glob_leetcode() @@ -100,13 +100,13 @@ impl<'app_lf> App<'app_lf> { }); true } - /// refresh `all_topic_qs`, `filtered_qs`, `topic_tags`, `difficultys` + /// refresh `all_topic_qs`, `filtered_qs`, `topic_tags`, `difficulties` pub async fn sync_new_done(&mut self) { - self.topic.sync_state = false; + self.topic.sync_bar.close(); let base = TopicTagsQS::base_info().await; - self.topic.all_topic_qs = base.0; - self.topic.topic_tags = base.1; - self.topic.difficultys = base + self.topic.question_state.all_qs = base.0; + self.topic.topic.topic_tags = base.1; + self.topic.difficulty.difficulties = base .2 .iter() .map(|v| v.0.clone()) diff --git a/crates/lcode/src/app/impl_app/get_info.rs b/crates/lcode/src/app/impl_app/get_info.rs index 09aedd5..e2b51d0 100644 --- a/crates/lcode/src/app/impl_app/get_info.rs +++ b/crates/lcode/src/app/impl_app/get_info.rs @@ -98,10 +98,10 @@ impl<'app> App<'app> { pub fn get_status_done(&mut self, info: (UserStatus, TotalPoints, PassData, PathBuf)) { ( - self.infos.user_status, - self.infos.points, - self.infos.pass_data, - self.infos.avatar_path, + self.info.user_status, + self.info.points, + self.info.pass_data, + self.info.avatar_path, ) = info; self.render(); diff --git a/crates/lcode/src/app/info/cmds/keymaps.rs b/crates/lcode/src/app/info/cmds/keymaps.rs new file mode 100644 index 0000000..d5b2762 --- /dev/null +++ b/crates/lcode/src/app/info/cmds/keymaps.rs @@ -0,0 +1,54 @@ +use ratatui::widgets::{ListItem, ListState}; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct KeymapState<'key> { + pub keymaps_state: ListState, + pub keymaps_items: Vec>, +} + +impl<'key> KeymapState<'key> { + pub fn new(keymaps_items: Vec>) -> Self { + Self { + keymaps_state: ListState::default(), + keymaps_items, + } + } + pub fn trigger(&self) -> bool { + let a = self + .keymaps_state + .selected() + .unwrap_or_default(); + if a == 0 { + crate::star(); + } + false + } + + pub fn first(&mut self) -> bool { + self.keymaps_state.select(Some(0)); + true + } + pub fn last(&mut self) -> bool { + self.keymaps_state + .select(Some(self.keymaps_items.len() - 1)); + true + } + pub fn prev(&mut self) -> bool { + let i = match self.keymaps_state.selected() { + Some(i) => (self.keymaps_items.len() + i - 1) % self.keymaps_items.len(), + None => 0, + }; + self.keymaps_state.select(Some(i)); + true + } + pub fn next(&mut self) -> bool { + let i = match self.keymaps_state.selected() { + Some(i) => (i + 1) % self.keymaps_items.len(), + None => 0, + }; + self.keymaps_state.select(Some(i)); + true + } +} diff --git a/crates/lcode/src/app/info/cmds/mod.rs b/crates/lcode/src/app/info/cmds/mod.rs new file mode 100644 index 0000000..dc59a38 --- /dev/null +++ b/crates/lcode/src/app/info/cmds/mod.rs @@ -0,0 +1 @@ +pub mod keymaps; diff --git a/crates/lcode/src/app/infos.rs b/crates/lcode/src/app/info/mod.rs similarity index 51% rename from crates/lcode/src/app/infos.rs rename to crates/lcode/src/app/info/mod.rs index a10b5eb..c77c482 100644 --- a/crates/lcode/src/app/infos.rs +++ b/crates/lcode/src/app/info/mod.rs @@ -1,19 +1,19 @@ +pub mod cmds; use std::path::PathBuf; use lcode_config::global::G_USER_CONFIG; use leetcode_api::leetcode::resps::{ checkin::TotalPoints, pass_qs::PassData, user_data::UserStatus, }; -use ratatui::widgets::{ListItem, ListState}; +use ratatui::widgets::ListItem; #[derive(Clone)] #[derive(Debug)] #[derive(Default)] -#[derive(PartialEq, Eq)] -pub struct Infos<'tab3> { - pub keymaps_state: ListState, - pub keymaps_items: Vec>, - pub user_status: UserStatus, +pub struct Info<'tab3> { + pub keymap: cmds::keymaps::KeymapState<'tab3>, + + pub user_status: UserStatus, pub points: TotalPoints, pub pass_data: PassData, @@ -21,7 +21,7 @@ pub struct Infos<'tab3> { } // keymaps -impl<'tab3> Infos<'tab3> { +impl<'tab3> Info<'tab3> { pub fn new() -> Self { let mut pat = Vec::with_capacity(G_USER_CONFIG.keymap.keymap.len() + 1); pat.push(ListItem::new( @@ -35,46 +35,26 @@ impl<'tab3> Infos<'tab3> { .map(|v| ListItem::new(v.to_string())); pat.extend(a); Self { + keymap: cmds::keymaps::KeymapState::new(pat), // image_status:ThreadProtocol::new(tx, inner), - keymaps_items: pat, ..Default::default() } } pub fn trigger(&self) -> bool { - let a = self - .keymaps_state - .selected() - .unwrap_or_default(); - if a == 0 { - crate::star(); - } - false + self.keymap.trigger() } pub fn first_item(&mut self) -> bool { - self.keymaps_state.select(Some(0)); - true + self.keymap.first() } pub fn last_item(&mut self) -> bool { - self.keymaps_state - .select(Some(self.keymaps_items.len() - 1)); - true + self.keymap.last() } pub fn prev_item(&mut self) -> bool { - let i = match self.keymaps_state.selected() { - Some(i) => (self.keymaps_items.len() + i - 1) % self.keymaps_items.len(), - None => 0, - }; - self.keymaps_state.select(Some(i)); - true + self.keymap.prev() } pub fn next_item(&mut self) -> bool { - let i = match self.keymaps_state.selected() { - Some(i) => (i + 1) % self.keymaps_items.len(), - None => 0, - }; - self.keymaps_state.select(Some(i)); - true + self.keymap.next() } } diff --git a/crates/lcode/src/app/inner.rs b/crates/lcode/src/app/inner.rs index cd77b86..c1c68f0 100644 --- a/crates/lcode/src/app/inner.rs +++ b/crates/lcode/src/app/inner.rs @@ -9,7 +9,7 @@ use tokio::{ }; use tui_textarea::TextArea; -use super::{dispatch::next_key, edit::EditCode, infos, select, topic, TuiIndex}; +use super::{dispatch::next_key, edit::EditCode, info, select, topic, TuiIndex}; use crate::{ editor::{CodeTestFile, Editor}, glob_leetcode, @@ -25,7 +25,7 @@ pub struct App<'app> { pub select: select::SelectQS<'app>, pub edit: EditCode<'app>, pub topic: topic::TopicTagsQS<'app>, - pub infos: infos::Infos<'app>, + pub info: info::Info<'app>, pub cur_qs: Question, @@ -106,7 +106,7 @@ impl<'app_lf> App<'app_lf> { .open(chf.code_path) .await .into_diagnostic()?; - for line in self.edit.code_block.lines() { + for line in self.edit.code_block.code_block.lines() { file.write_all(line.as_bytes()) .await .into_diagnostic()?; @@ -129,7 +129,7 @@ impl<'app_lf> App<'app_lf> { return Ok(()); } - self.edit.code_block = TextArea::default(); + self.edit.code_block.code_block = TextArea::default(); let pb = Query::get_question_index(&IdSlug::Slug(qs.qs_slug.clone().unwrap_or_default())) .await?; @@ -158,10 +158,19 @@ impl<'app_lf> App<'app_lf> { .await .into_diagnostic()? { - self.edit.code_block.insert_str(line); - self.edit.code_block.insert_newline(); + self.edit + .code_block + .code_block + .insert_str(line); + self.edit + .code_block + .code_block + .insert_newline(); } - self.edit.code_block.delete_newline(); + self.edit + .code_block + .code_block + .delete_newline(); Ok(()) } @@ -172,14 +181,14 @@ impl<'app_lf> App<'app_lf> { pub async fn new(events: EventsHandler) -> App<'app_lf> { let tab0 = select::SelectQS::new().await; let tab2 = topic::TopicTagsQS::new().await; - let tab3 = infos::Infos::new(); + let tab3 = info::Info::new(); Self { - titles: ["select", "edit", "select with topic", "infos"].into(), + titles: ["select", "edit", "select with topic", "info"].into(), select: tab0, topic: tab2, - infos: tab3, + info: tab3, next_key: next_key::NextKey { keymaps: Vec::new(), times: 0 }, diff --git a/crates/lcode/src/app/mod.rs b/crates/lcode/src/app/mod.rs index 415f80c..12033df 100644 --- a/crates/lcode/src/app/mod.rs +++ b/crates/lcode/src/app/mod.rs @@ -1,7 +1,7 @@ mod dispatch; mod edit; mod impl_app; -mod infos; +mod info; pub mod inner; mod select; mod topic; @@ -17,7 +17,7 @@ pub enum TuiIndex { Select, Edit, Topic, - Infos, + Info, } impl From for usize { @@ -26,7 +26,7 @@ impl From for usize { TuiIndex::Select => 0, TuiIndex::Edit => 1, TuiIndex::Topic => 2, - TuiIndex::Infos => 3, + TuiIndex::Info => 3, } } } @@ -36,16 +36,16 @@ impl TuiIndex { *self = match self { Self::Select => Self::Edit, Self::Edit => Self::Topic, - Self::Topic => Self::Infos, - Self::Infos => Self::Select, + Self::Topic => Self::Info, + Self::Info => Self::Select, }; } fn prev(&mut self) { *self = match self { - Self::Select => Self::Infos, + Self::Select => Self::Info, Self::Edit => Self::Select, Self::Topic => Self::Edit, - Self::Infos => Self::Topic, + Self::Info => Self::Topic, }; } } diff --git a/crates/lcode/src/app/select.rs b/crates/lcode/src/app/select.rs deleted file mode 100644 index d58644b..0000000 --- a/crates/lcode/src/app/select.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crossterm::event::Event as CrossEvent; -use leetcode_api::{dao::query::Query, entities::index}; -use ratatui::widgets::TableState; -use rayon::prelude::*; -use tui_textarea::{Input, TextArea}; - -use super::TuiMode; -use crate::fuzzy_search::filter; - -// tab0 select questions -#[derive(Clone)] -#[derive(Default)] -#[derive(Debug)] -pub struct SelectQS<'tab0> { - pub all_questions: Box<[index::Model]>, - pub filtered_qs: Box<[index::Model]>, - pub state: TableState, - - pub sync_state: bool, - pub cur_perc: f64, - - pub input_line_mode: TuiMode, - pub text_line: TextArea<'tab0>, -} - -impl<'tab0> SelectQS<'tab0> { - pub fn keymap_insert(&mut self, event: CrossEvent) -> bool { - match event.into() { - Input { key: tui_textarea::Key::Esc, .. } => self.out_edit(), - Input { key: tui_textarea::Key::Enter, .. } => false, - input => self.text_line.input(input), // Use default key mappings in insert mode(emacs) - }; - self.filter_by_input(); - true - } -} - -impl<'tab0> SelectQS<'tab0> { - pub async fn new() -> SelectQS<'tab0> { - let questions = Query::query_all_index() - .await - .unwrap_or_default(); - - Self { - all_questions: questions.clone().into(), - filtered_qs: questions.into(), - - sync_state: false, - cur_perc: 0.0, - - ..Default::default() - } - } - pub fn update_percent(&mut self, cur_perc: f64) { - self.cur_perc = cur_perc; - } - /// refresh `filtered_qs` - pub fn filter_by_input(&mut self) { - self.filtered_qs = self - .all_questions - .par_iter() - .filter(|v| filter(&self.text_line.lines()[0], &v.to_string())) - .cloned() - .collect(); - } - - /// next question item - pub fn next_qs(&mut self) -> bool { - let i = self - .state - .selected() - .map_or(0, |i| (i + 1) % self.filtered_qs.len().max(1)); - self.state.select(Some(i)); - true - } - - /// previous question item - pub fn prev_qs(&mut self) -> bool { - let len = self.filtered_qs.len().max(1); - let i = self - .state - .selected() - .map_or(0, |i| (len + i - 1) % len); - self.state.select(Some(i)); - true - } - /// first question item - pub fn first_qs(&mut self) -> bool { - self.state.select(Some(0)); - true - } - /// last question item - pub fn last_qs(&mut self) -> bool { - self.state - .select(Some(self.filtered_qs.len().saturating_sub(1))); - true - } - - /// current selected question id - pub fn current_qs(&self) -> u32 { - self.state - .selected() - .map_or(0, |index| { - self.filtered_qs - .get(index) - .cloned() - .unwrap_or_default() - .question_id - }) - } - - /// enter input line - pub fn edit(&mut self) -> bool { - self.input_line_mode = TuiMode::Insert; - true - } - pub fn out_edit(&mut self) -> bool { - self.input_line_mode = TuiMode::OutEdit; - true - } -} diff --git a/crates/lcode/src/app/select/cmds/inputline.rs b/crates/lcode/src/app/select/cmds/inputline.rs new file mode 100644 index 0000000..263c389 --- /dev/null +++ b/crates/lcode/src/app/select/cmds/inputline.rs @@ -0,0 +1,26 @@ +use tui_textarea::{Input, TextArea}; + +use crate::app::TuiMode; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct InputLine<'line> { + pub mode: TuiMode, + pub text_line: TextArea<'line>, +} + +impl<'line> InputLine<'line> { + pub fn handle_input(&mut self, input: Input) -> bool { + self.text_line.input(input) + } + pub fn first_line(&self) -> &str { + &self.text_line.lines()[0] + } + pub fn insert(&mut self) { + self.mode = TuiMode::Insert; + } + pub fn out_edit(&mut self) { + self.mode = TuiMode::OutEdit; + } +} diff --git a/crates/lcode/src/app/select/cmds/mod.rs b/crates/lcode/src/app/select/cmds/mod.rs new file mode 100644 index 0000000..addee5b --- /dev/null +++ b/crates/lcode/src/app/select/cmds/mod.rs @@ -0,0 +1,3 @@ +pub mod inputline; +pub mod question; +pub mod sync_bar; diff --git a/crates/lcode/src/app/select/cmds/question.rs b/crates/lcode/src/app/select/cmds/question.rs new file mode 100644 index 0000000..6fee9c6 --- /dev/null +++ b/crates/lcode/src/app/select/cmds/question.rs @@ -0,0 +1,64 @@ +use leetcode_api::entities::index; +use ratatui::widgets::TableState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct QsState { + pub all_questions: Box<[index::Model]>, + pub filtered_qs: Box<[index::Model]>, + pub state: TableState, +} + +impl QsState { + pub fn new(all_questions: Box<[index::Model]>) -> Self { + let filtered_qs = all_questions.clone(); + Self { + all_questions, + filtered_qs, + state: TableState::default(), + } + } + const fn selected(&self) -> Option { + self.state.selected() + } + const fn filtered_qs_len(&self) -> usize { + self.filtered_qs.len() + } + + pub fn current_qs(&self) -> u32 { + self.selected().map_or(0, |index| { + self.filtered_qs + .get(index) + .cloned() + .unwrap_or_default() + .question_id + }) + } + + pub fn first(&mut self) -> bool { + self.state.select(Some(0)); + true + } + pub fn last(&mut self) -> bool { + self.state + .select(Some(self.filtered_qs_len().saturating_sub(1))); + true + } + pub fn next(&mut self) -> bool { + let i = self + .selected() + .map_or(0, |i| (i + 1) % self.filtered_qs_len().max(1)); + self.state.select(Some(i)); + true + } + pub fn prev(&mut self) -> bool { + let len = self.filtered_qs_len().max(1); + let i = self + .state + .selected() + .map_or(0, |i| (len + i - 1) % len); + self.state.select(Some(i)); + true + } +} diff --git a/crates/lcode/src/app/select/cmds/sync_bar.rs b/crates/lcode/src/app/select/cmds/sync_bar.rs new file mode 100644 index 0000000..38fedaf --- /dev/null +++ b/crates/lcode/src/app/select/cmds/sync_bar.rs @@ -0,0 +1,16 @@ +#[derive(Clone, Copy)] +#[derive(Debug)] +#[derive(Default)] +pub struct BarState { + pub show: bool, + pub cur_perc: f64, +} + +impl BarState { + pub fn update(&mut self, cur_perc: f64) { + self.cur_perc = cur_perc; + } + pub fn close(&mut self) { + self.show = false; + } +} diff --git a/crates/lcode/src/app/select/mod.rs b/crates/lcode/src/app/select/mod.rs new file mode 100644 index 0000000..e2dca7b --- /dev/null +++ b/crates/lcode/src/app/select/mod.rs @@ -0,0 +1,98 @@ +pub mod cmds; + +use crossterm::event::Event as CrossEvent; +use leetcode_api::dao::query::Query; +use rayon::prelude::*; +use tui_textarea::Input; + +use self::cmds::{inputline, question, sync_bar}; +use crate::fuzzy_search::filter; + +// tab0 select questions +#[derive(Clone)] +#[derive(Default)] +#[derive(Debug)] +pub struct SelectQS<'tab0> { + pub qs_state: question::QsState, + pub sync_bar: sync_bar::BarState, + pub inputline: inputline::InputLine<'tab0>, +} + +impl<'tab0> SelectQS<'tab0> { + pub fn keymap_insert(&mut self, event: CrossEvent) -> bool { + match event.into() { + Input { key: tui_textarea::Key::Esc, .. } => self.out_edit(), + Input { key: tui_textarea::Key::Enter, .. } => false, // just one line so do nothing + input => { + // Use default key mappings in insert mode(emacs) + let trigger = self.inputline.handle_input(input); + if trigger { + self.filter_by_input(); + } + trigger + }, + } + } +} + +impl<'tab0> SelectQS<'tab0> { + pub async fn new() -> SelectQS<'tab0> { + let questions = Query::query_all_index() + .await + .unwrap_or_default(); + + Self { + qs_state: question::QsState::new(questions.into()), + + sync_bar: sync_bar::BarState::default(), + + ..Default::default() + } + } + pub fn update_percent(&mut self, cur_perc: f64) { + self.sync_bar.update(cur_perc); + } + /// refresh `filtered_qs` + pub fn filter_by_input(&mut self) { + self.qs_state.filtered_qs = self + .qs_state + .all_questions + .par_iter() + .filter(|v| filter(self.inputline.first_line(), &v.to_string())) + .cloned() + .collect(); + } + + /// next question item + pub fn next_qs(&mut self) -> bool { + self.qs_state.next() + } + + /// previous question item + pub fn prev_qs(&mut self) -> bool { + self.qs_state.prev() + } + /// first question item + pub fn first_qs(&mut self) -> bool { + self.qs_state.first() + } + /// last question item + pub fn last_qs(&mut self) -> bool { + self.qs_state.last() + } + + /// current selected question id + pub fn current_qs(&self) -> u32 { + self.qs_state.current_qs() + } + + /// enter input line + pub fn edit(&mut self) -> bool { + self.inputline.insert(); + true + } + pub fn out_edit(&mut self) -> bool { + self.inputline.out_edit(); + true + } +} diff --git a/crates/lcode/src/app/topic.rs b/crates/lcode/src/app/topic.rs deleted file mode 100644 index b920e73..0000000 --- a/crates/lcode/src/app/topic.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crossterm::event::Event as CrossEvent; -use leetcode_api::{ - dao::query::{self, Query}, - entities::{new_index, topic_tags}, -}; -use ratatui::widgets::ListState; -use rayon::prelude::*; -use tui_textarea::{Input, TextArea}; - -use super::TuiMode; -use crate::fuzzy_search::filter; - -/// tui layout position -/// -/// | | | | -/// | `AllTopics` | `Difficulty` | | -/// | ========== | ========== | `Questions` | -/// | `UserTopics` | | | -/// | | | | -#[derive(PartialEq, Eq)] -#[derive(Clone, Copy)] -#[derive(Default)] -#[derive(Debug)] -pub enum Tab2Panel { - #[default] - AllTopics, - UserTopics, - Difficulty, - Questions, -} - -impl Tab2Panel { - fn left(&mut self) { - *self = match self { - Self::AllTopics | Self::Difficulty => Self::AllTopics, - Self::UserTopics => Self::UserTopics, - Self::Questions => Self::Difficulty, - } - } - fn right(&mut self) { - *self = match self { - Self::AllTopics | Self::UserTopics => Self::Difficulty, - Self::Difficulty | Self::Questions => Self::Questions, - } - } - fn up(&mut self) { - *self = match self { - Self::AllTopics | Self::UserTopics => Self::AllTopics, - Self::Difficulty => Self::Difficulty, - Self::Questions => Self::Questions, - } - } - fn down(&mut self) { - *self = match self { - Self::AllTopics | Self::UserTopics => Self::UserTopics, - Self::Difficulty => Self::Difficulty, - Self::Questions => Self::Questions, - } - } -} - -#[derive(Clone)] -#[derive(Default)] -#[derive(Debug)] -pub struct TopicTagsQS<'tab2> { - pub topic_tags: Box<[topic_tags::Model]>, - pub topic_tags_state: ListState, - - pub all_topic_qs: Box<[new_index::Model]>, - pub filtered_topic_qs_state: ListState, - pub filtered_qs: Box<[new_index::Model]>, - - pub user_topic_tags: Vec, - pub user_topic_tags_translated: Vec, - pub user_topic_tags_state: ListState, - - pub sync_state: bool, - pub cur_perc: f64, - - pub index: Tab2Panel, - - pub text_line: TextArea<'tab2>, - pub input_line_mode: TuiMode, - - pub user_diff: String, - pub difficultys: Box<[String]>, - pub difficultys_state: ListState, - - pub ac_status: Box<[(String, u32, u32)]>, -} - -impl<'tab2> TopicTagsQS<'tab2> { - pub fn keymap_insert(&mut self, event: CrossEvent) -> bool { - match event.into() { - Input { key: tui_textarea::Key::Esc, .. } => self.be_out_edit(), - Input { key: tui_textarea::Key::Enter, .. } => false, - input => self.text_line.input(input), // Use default key mappings in insert mode(emacs) - }; - self.refresh_filter_by_input(); - true - } - pub fn be_out_edit(&mut self) -> bool { - self.input_line_mode = TuiMode::OutEdit; - true - } - pub fn enter_input_line(&mut self) -> bool { - self.input_line_mode = TuiMode::Insert; - true - } - - pub fn up(&mut self) -> bool { - match self.index { - Tab2Panel::AllTopics => self.prev_topic(), - Tab2Panel::UserTopics => self.prev_user_topic(), - Tab2Panel::Difficulty => self.prev_diff(), - Tab2Panel::Questions => self.prev_qs(), - } - true - } - pub fn down(&mut self) -> bool { - match self.index { - Tab2Panel::AllTopics => self.next_topic(), - Tab2Panel::UserTopics => self.next_user_topic(), - Tab2Panel::Difficulty => self.next_diff(), - Tab2Panel::Questions => self.next_qs(), - } - true - } - pub fn panel_left(&mut self) -> bool { - self.index.left(); - true - } - pub fn panel_right(&mut self) -> bool { - self.index.right(); - true - } - pub fn panel_up(&mut self) -> bool { - self.index.up(); - true - } - pub fn panel_down(&mut self) -> bool { - self.index.down(); - true - } - pub fn top(&mut self) -> bool { - match self.index { - Tab2Panel::AllTopics => self.first_topic(), - Tab2Panel::UserTopics => self.first_user_topic(), - Tab2Panel::Difficulty => self.first_diff(), - Tab2Panel::Questions => self.first_qs(), - } - true - } - pub fn bottom(&mut self) -> bool { - match self.index { - Tab2Panel::AllTopics => self.last_topic(), - Tab2Panel::UserTopics => self.last_user_topic(), - Tab2Panel::Difficulty => self.last_diff(), - Tab2Panel::Questions => self.last_qs(), - } - true - } - pub async fn toggle_cursor(&mut self) -> bool { - match self.index { - Tab2Panel::AllTopics => self.add_user_topic().await, - Tab2Panel::UserTopics => self.rm_user_topic().await, - Tab2Panel::Difficulty => self.toggle_diff().await, - Tab2Panel::Questions => {}, - } - true - } -} - -// for `difficultys` -impl<'tab2> TopicTagsQS<'tab2> { - pub async fn toggle_diff(&mut self) { - let index = self - .difficultys_state - .selected() - .unwrap_or_default(); - let diff = self - .difficultys - .get(index) - .expect("get difficulty failed"); - if self.user_diff == *diff { - self.user_diff = String::new(); - } - else { - self.user_diff.clone_from(diff); - } - self.refresh_filter_by_topic_diff() - .await; - self.refresh_filter_by_input(); - } - pub fn prev_diff(&mut self) { - let len = self.difficultys.len().max(1); - let i = self - .difficultys_state - .selected() - .map_or(0, |i| (len + i - 1) % len); - self.difficultys_state.select(Some(i)); - } - pub fn next_diff(&mut self) { - let len = self.difficultys.len().max(1); - let i = self - .difficultys_state - .selected() - .map_or(0, |i| (len + i + 1) % len); - self.difficultys_state.select(Some(i)); - } - pub fn first_diff(&mut self) { - self.difficultys_state.select(Some(0)); - } - pub fn last_diff(&mut self) { - self.difficultys_state - .select(Some(self.difficultys.len())); - } -} - -impl<'tab2> TopicTagsQS<'tab2> { - pub async fn new() -> TopicTagsQS<'tab2> { - let (new_index, topic_tags, ac_status) = Self::base_info().await; - - Self { - topic_tags, - topic_tags_state: ListState::default(), - - all_topic_qs: new_index.clone(), - filtered_topic_qs_state: ListState::default(), - filtered_qs: new_index, - - user_topic_tags: vec![], - user_topic_tags_translated: vec![], - user_topic_tags_state: ListState::default(), - - sync_state: false, - cur_perc: 0.0, - - index: Tab2Panel::AllTopics, - - text_line: TextArea::default(), - input_line_mode: TuiMode::default(), - - user_diff: String::new(), - difficultys: ac_status - .iter() - .map(|v| v.0.clone()) - .collect(), - difficultys_state: ListState::default(), - - ac_status, - } - } - - /// return `new_index`, `topic_tags`, `ac_status` - pub async fn base_info() -> ( - Box<[new_index::Model]>, - Box<[topic_tags::Model]>, - Box<[(String, u32, u32)]>, - ) { - let (all_qs_res, topic_res, status) = tokio::join!( - query::Query::query_all_new_index(None), - query::Query::query_all_topic(), - query::Query::query_status() - ); - ( - all_qs_res.unwrap_or_default().into(), - topic_res.unwrap_or_default().into(), - status.unwrap_or_default().into(), - ) - } - - pub fn update_percent(&mut self, cur_perc: f64) { - self.cur_perc = cur_perc; - } - /// refresh `filtered_qs` - pub fn refresh_filter_by_input(&mut self) { - self.filtered_qs = self - .all_topic_qs - .par_iter() - .filter(|&v| filter(&self.text_line.lines()[0], &v.to_string())) - .cloned() - .collect(); - } - /// refresh `all_topic_qs` - pub async fn refresh_filter_by_topic_diff(&mut self) { - if self.user_topic_tags.is_empty() { - self.all_topic_qs = Query::query_all_new_index(Some(self.user_diff.clone())) - .await - .unwrap_or_default() - .into(); - } - else { - let diff = self.user_diff.clone(); - self.all_topic_qs = Query::query_by_topic(&self.user_topic_tags, Some(diff)) - .await - .unwrap_or_default() - .into(); - } - } -} - -// all topic tags, add remove topic -impl<'tab2> TopicTagsQS<'tab2> { - pub async fn rm_user_topic(&mut self) { - let cur_top = self - .user_topic_tags_state - .selected() - .unwrap_or_default(); - - if !self.user_topic_tags.is_empty() { - self.user_topic_tags.remove(cur_top); - self.user_topic_tags_translated - .remove(cur_top); - } - if cur_top >= self.user_topic_tags.len() { - self.prev_user_topic(); - } - - self.refresh_filter_by_topic_diff() - .await; - self.refresh_filter_by_input(); - } - - pub async fn add_user_topic(&mut self) { - let cur_top = self - .topic_tags_state - .selected() - .unwrap_or_default(); - - let (topic_slug, translated_slug) = self - .topic_tags - .get(cur_top) - .map(|v| { - ( - v.topic_slug.clone(), - v.name_translated - .clone() - .unwrap_or_default(), - ) - }) - .unwrap_or_default(); - - if !self - .user_topic_tags - .contains(&topic_slug) - { - self.user_topic_tags_translated.push( - if translated_slug.is_empty() { - topic_slug.clone() - } - else { - translated_slug - }, - ); - self.user_topic_tags.push(topic_slug); - } - self.refresh_filter_by_topic_diff() - .await; - self.refresh_filter_by_input(); - } - - // topic_tags ////////////////////////////////// - pub fn first_topic(&mut self) { - self.topic_tags_state.select(Some(0)); - } - pub fn last_topic(&mut self) { - self.topic_tags_state - .select(Some(self.topic_tags.len() - 1)); - } - pub fn next_topic(&mut self) { - let i = self - .topic_tags_state - .selected() - .map_or(0, |i| { - i.saturating_add(1) - .min(self.topic_tags.len().saturating_sub(1)) - }); - self.topic_tags_state.select(Some(i)); - } - pub fn prev_topic(&mut self) { - let i = self - .topic_tags_state - .selected() - .map_or(0, |i| i.saturating_sub(1)); - self.topic_tags_state.select(Some(i)); - } -} - -// filtered questions -impl<'tab2> TopicTagsQS<'tab2> { - pub fn next_qs(&mut self) { - let index = self - .filtered_topic_qs_state - .selected() - .map_or(0, |i| (i + 1) % self.filtered_qs.len().max(1)); - self.filtered_topic_qs_state - .select(Some(index)); - } - pub fn prev_qs(&mut self) { - let len = self.filtered_qs.len().max(1); - let index = self - .filtered_topic_qs_state - .selected() - .map_or(0, |i| (len + i - 1) % len); - self.filtered_topic_qs_state - .select(Some(index)); - } - pub fn first_qs(&mut self) { - self.filtered_topic_qs_state - .select(Some(0)); - } - pub fn last_qs(&mut self) { - self.filtered_topic_qs_state - .select(Some(self.filtered_qs.len() - 1)); - } - pub fn cur_qs_slug(&self) -> Option { - let index = self - .filtered_topic_qs_state - .selected() - .unwrap_or_default(); - self.filtered_qs - .get(index) - .map(|v| v.title_slug.clone()) - } -} - -// user topic tags -impl<'tab2> TopicTagsQS<'tab2> { - pub fn prev_user_topic(&mut self) { - let index = self - .user_topic_tags_state - .selected() - .map_or(0, |i| i.saturating_sub(1)); - self.user_topic_tags_state - .select(Some(index)); - } - - pub fn next_user_topic(&mut self) { - let index = self - .user_topic_tags_state - .selected() - .map_or(0, |i| { - i.saturating_add(1).min( - self.user_topic_tags - .len() - .saturating_sub(1), - ) - }); - self.user_topic_tags_state - .select(Some(index)); - } - pub fn last_user_topic(&mut self) { - self.user_topic_tags_state - .select(Some(self.user_topic_tags.len() - 1)); - } - pub fn first_user_topic(&mut self) { - self.user_topic_tags_state - .select(Some(0)); - } -} diff --git a/crates/lcode/src/app/topic/cmds/diff.rs b/crates/lcode/src/app/topic/cmds/diff.rs new file mode 100644 index 0000000..36143cc --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/diff.rs @@ -0,0 +1,59 @@ +use ratatui::widgets::ListState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct DiffState { + pub user_diff: String, + pub difficulties: Box<[String]>, + pub diff_list_state: ListState, +} + +impl DiffState { + pub fn new(difficulties: Box<[String]>) -> Self { + Self { + user_diff: String::new(), + difficulties, + diff_list_state: ListState::default(), + } + } + pub fn toggle_diff(&mut self) { + let index = self + .diff_list_state + .selected() + .unwrap_or_default(); + let diff = self + .difficulties + .get(index) + .expect("get difficulty failed"); + if self.user_diff == *diff { + self.user_diff = String::new(); + } + else { + self.user_diff.clone_from(diff); + } + } + pub fn first(&mut self) { + self.diff_list_state.select(Some(0)); + } + pub fn last(&mut self) { + self.diff_list_state + .select(Some(self.difficulties.len())); + } + pub fn prev(&mut self) { + let len = self.difficulties.len().max(1); + let i = self + .diff_list_state + .selected() + .map_or(0, |i| (len + i - 1) % len); + self.diff_list_state.select(Some(i)); + } + pub fn next(&mut self) { + let len = self.difficulties.len().max(1); + let i = self + .diff_list_state + .selected() + .map_or(0, |i| (len + i + 1) % len); + self.diff_list_state.select(Some(i)); + } +} diff --git a/crates/lcode/src/app/topic/cmds/intputline.rs b/crates/lcode/src/app/topic/cmds/intputline.rs new file mode 100644 index 0000000..c7df055 --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/intputline.rs @@ -0,0 +1,27 @@ +use tui_textarea::{Input, TextArea}; + +use crate::app::TuiMode; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct InputLine<'line> { + pub text_line: TextArea<'line>, + pub mode: TuiMode, +} + +impl<'line> InputLine<'line> { + /// return if the input modified text contents or not + pub fn handle_input(&mut self, input: Input) -> bool { + self.text_line.input(input) + } + pub fn out_edit(&mut self) { + self.mode = TuiMode::OutEdit; + } + pub fn enter_input(&mut self) { + self.mode = TuiMode::Insert; + } + pub fn first_line(&self) -> &str { + &self.text_line.lines()[0] + } +} diff --git a/crates/lcode/src/app/topic/cmds/mod.rs b/crates/lcode/src/app/topic/cmds/mod.rs new file mode 100644 index 0000000..67446b2 --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/mod.rs @@ -0,0 +1,5 @@ +pub mod diff; +pub mod intputline; +pub mod question; +pub mod sync_bar; +pub mod topics; diff --git a/crates/lcode/src/app/topic/cmds/question.rs b/crates/lcode/src/app/topic/cmds/question.rs new file mode 100644 index 0000000..ff39d82 --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/question.rs @@ -0,0 +1,52 @@ +use leetcode_api::entities::new_index; +use ratatui::widgets::ListState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct TopicQuestionState { + pub all_qs: Box<[new_index::Model]>, + pub filtered_topic_qs_state: ListState, + pub filtered_qs: Box<[new_index::Model]>, +} + +impl TopicQuestionState { + pub fn new(all_qs: Box<[new_index::Model]>) -> Self { + let filtered_qs = all_qs.clone(); + Self { + all_qs, + filtered_topic_qs_state: ListState::default(), + filtered_qs, + } + } + pub fn next(&mut self) { + let index = self + .filtered_topic_qs_state + .selected() + .map_or(0, |i| (i + 1) % self.filtered_qs.len().max(1)); + self.filtered_topic_qs_state + .select(Some(index)); + } + pub fn prev(&mut self) { + let len = self.filtered_qs.len().max(1); + let index = self + .filtered_topic_qs_state + .selected() + .map_or(0, |i| (len + i - 1) % len); + self.filtered_topic_qs_state + .select(Some(index)); + } + pub fn last(&mut self) { + self.filtered_topic_qs_state + .select(Some(self.filtered_qs.len() - 1)); + } + pub fn cur_qs_slug(&self) -> Option { + let index = self + .filtered_topic_qs_state + .selected() + .unwrap_or_default(); + self.filtered_qs + .get(index) + .map(|v| v.title_slug.clone()) + } +} diff --git a/crates/lcode/src/app/topic/cmds/sync_bar.rs b/crates/lcode/src/app/topic/cmds/sync_bar.rs new file mode 100644 index 0000000..97c8499 --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/sync_bar.rs @@ -0,0 +1,17 @@ +#[derive(Clone, Copy)] +#[derive(Debug)] +#[derive(Default)] +pub struct BarState { + pub show: bool, + pub cur_perc: f64, +} + +impl BarState { + pub fn close(&mut self) { + self.show = false; + } + + pub fn update(&mut self, cur_perc: f64) { + self.cur_perc = cur_perc; + } +} diff --git a/crates/lcode/src/app/topic/cmds/topics.rs b/crates/lcode/src/app/topic/cmds/topics.rs new file mode 100644 index 0000000..2a3a9f0 --- /dev/null +++ b/crates/lcode/src/app/topic/cmds/topics.rs @@ -0,0 +1,144 @@ +use leetcode_api::entities::topic_tags; +use ratatui::widgets::ListState; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Default)] +pub struct TopicsState { + pub topic_tags: Box<[topic_tags::Model]>, + pub topic_tags_state: ListState, + + pub user_topic_tags: Vec, + pub user_topic_tags_translated: Vec, + pub user_topic_tags_state: ListState, +} + +impl TopicsState { + /// if remove a topic return `true` or `false` + pub fn rm_user_topic(&mut self) -> bool { + let mut trigger = false; + let cur_top = self + .user_topic_tags_state + .selected() + .unwrap_or_default(); + + if !self.user_topic_tags.is_empty() { + self.user_topic_tags.remove(cur_top); + self.user_topic_tags_translated + .remove(cur_top); + trigger = true; + } + if cur_top >= self.user_topic_tags.len() { + self.prev_user(); + } + trigger + } + /// if add a topic return `true` or `false` + pub fn add_user_topic(&mut self) -> bool { + let mut trigger = false; + let cur_top = self + .topic_tags_state + .selected() + .unwrap_or_default(); + + let (topic_slug, translated_slug) = self + .topic_tags + .get(cur_top) + .map(|v| { + ( + v.topic_slug.clone(), + v.name_translated + .clone() + .unwrap_or_default(), + ) + }) + .unwrap_or_default(); + + if !self + .user_topic_tags + .contains(&topic_slug) + { + self.user_topic_tags_translated.push( + if translated_slug.is_empty() { + topic_slug.clone() + } + else { + translated_slug + }, + ); + self.user_topic_tags.push(topic_slug); + trigger = true; + } + trigger + } +} + +impl TopicsState { + pub fn prev_user(&mut self) { + let index = self + .user_topic_tags_state + .selected() + .map_or(0, |i| i.saturating_sub(1)); + self.user_topic_tags_state + .select(Some(index)); + } + pub fn next_user(&mut self) { + let index = self + .user_topic_tags_state + .selected() + .map_or(0, |i| { + i.saturating_add(1).min( + self.user_topic_tags + .len() + .saturating_sub(1), + ) + }); + self.user_topic_tags_state + .select(Some(index)); + } + pub fn last_user(&mut self) { + self.user_topic_tags_state + .select(Some(self.user_topic_tags.len() - 1)); + } + pub fn first_user(&mut self) { + self.user_topic_tags_state + .select(Some(0)); + } +} + +impl TopicsState { + pub fn new(topic_tags: Box<[topic_tags::Model]>) -> Self { + Self { + topic_tags, + topic_tags_state: ListState::default(), + + user_topic_tags: vec![], + user_topic_tags_translated: vec![], + user_topic_tags_state: ListState::default(), + } + } + pub fn first(&mut self) { + self.topic_tags_state.select(Some(0)); + } + pub fn last(&mut self) { + self.topic_tags_state + .select(Some(self.topic_tags.len() - 1)); + } + pub fn next(&mut self) { + let i = self + .topic_tags_state + .selected() + .map_or(0, |i| { + i.saturating_add(1) + .min(self.topic_tags.len().saturating_sub(1)) + }); + self.topic_tags_state.select(Some(i)); + } + pub fn prev(&mut self) { + let i = self + .topic_tags_state + .selected() + .map_or(0, |i| i.saturating_sub(1)); + self.topic_tags_state.select(Some(i)); + } +} diff --git a/crates/lcode/src/app/topic/mod.rs b/crates/lcode/src/app/topic/mod.rs new file mode 100644 index 0000000..2c357e0 --- /dev/null +++ b/crates/lcode/src/app/topic/mod.rs @@ -0,0 +1,339 @@ +pub mod cmds; + +use crossterm::event::Event as CrossEvent; +use leetcode_api::{ + dao::query::{self, Query}, + entities::{new_index, topic_tags}, +}; +use rayon::prelude::*; +use tui_textarea::Input; + +use self::cmds::{diff, intputline, question, sync_bar, topics}; +use crate::fuzzy_search::filter; + +/// tui layout position +/// +/// | | | | +/// | `AllTopics` | `Difficulty` | | +/// | ========== | ========== | `Questions` | +/// | `UserTopics` | | | +/// | | | | +#[derive(PartialEq, Eq)] +#[derive(Clone, Copy)] +#[derive(Default)] +#[derive(Debug)] +pub enum Tab2Panel { + #[default] + AllTopics, + UserTopics, + Difficulty, + Questions, +} + +impl Tab2Panel { + fn left(&mut self) { + *self = match self { + Self::AllTopics | Self::Difficulty => Self::AllTopics, + Self::UserTopics => Self::UserTopics, + Self::Questions => Self::Difficulty, + } + } + fn right(&mut self) { + *self = match self { + Self::AllTopics | Self::UserTopics => Self::Difficulty, + Self::Difficulty | Self::Questions => Self::Questions, + } + } + fn up(&mut self) { + *self = match self { + Self::AllTopics | Self::UserTopics => Self::AllTopics, + Self::Difficulty => Self::Difficulty, + Self::Questions => Self::Questions, + } + } + fn down(&mut self) { + *self = match self { + Self::AllTopics | Self::UserTopics => Self::UserTopics, + Self::Difficulty => Self::Difficulty, + Self::Questions => Self::Questions, + } + } +} + +#[derive(Clone)] +#[derive(Default)] +#[derive(Debug)] +pub struct TopicTagsQS<'tab2> { + pub topic: topics::TopicsState, + pub question_state: question::TopicQuestionState, + pub sync_bar: sync_bar::BarState, + pub index: Tab2Panel, + pub inputline: intputline::InputLine<'tab2>, + pub difficulty: diff::DiffState, + pub ac_status: Box<[(String, u32, u32)]>, +} + +impl<'tab2> TopicTagsQS<'tab2> { + pub fn keymap_insert(&mut self, event: CrossEvent) -> bool { + match event.into() { + Input { key: tui_textarea::Key::Esc, .. } => self.be_out_edit(), + Input { key: tui_textarea::Key::Enter, .. } => false, // just one line so do nothing + input => { + // Use default key mappings in insert mode(emacs) + let trigger = self.inputline.handle_input(input); + if trigger { + self.refresh_filter_by_input(); + } + trigger + }, + } + } + pub fn be_out_edit(&mut self) -> bool { + self.inputline.out_edit(); + true + } + pub fn enter_input_line(&mut self) -> bool { + self.inputline.enter_input(); + true + } + + pub fn up(&mut self) -> bool { + match self.index { + Tab2Panel::AllTopics => self.prev_topic(), + Tab2Panel::UserTopics => self.prev_user_topic(), + Tab2Panel::Difficulty => self.prev_diff(), + Tab2Panel::Questions => self.prev_qs(), + } + true + } + pub fn down(&mut self) -> bool { + match self.index { + Tab2Panel::AllTopics => self.next_topic(), + Tab2Panel::UserTopics => self.next_user_topic(), + Tab2Panel::Difficulty => self.next_diff(), + Tab2Panel::Questions => self.next_qs(), + } + true + } + pub fn panel_left(&mut self) -> bool { + self.index.left(); + true + } + pub fn panel_right(&mut self) -> bool { + self.index.right(); + true + } + pub fn panel_up(&mut self) -> bool { + self.index.up(); + true + } + pub fn panel_down(&mut self) -> bool { + self.index.down(); + true + } + pub fn top(&mut self) -> bool { + match self.index { + Tab2Panel::AllTopics => self.first_topic(), + Tab2Panel::UserTopics => self.first_user_topic(), + Tab2Panel::Difficulty => self.first_diff(), + Tab2Panel::Questions => self.first_qs(), + } + true + } + pub fn bottom(&mut self) -> bool { + match self.index { + Tab2Panel::AllTopics => self.last_topic(), + Tab2Panel::UserTopics => self.last_user_topic(), + Tab2Panel::Difficulty => self.last_diff(), + Tab2Panel::Questions => self.last_qs(), + } + true + } + pub async fn toggle_cursor(&mut self) -> bool { + match self.index { + Tab2Panel::AllTopics => self.add_user_topic().await, + Tab2Panel::UserTopics => self.rm_user_topic().await, + Tab2Panel::Difficulty => self.toggle_diff().await, + Tab2Panel::Questions => true, + } + } +} + +// for `difficulties` +impl<'tab2> TopicTagsQS<'tab2> { + pub async fn toggle_diff(&mut self) -> bool { + // the operate must trigger refresh + self.difficulty.toggle_diff(); + + self.refresh_filter_by_topic_diff() + .await; + self.refresh_filter_by_input(); + true + } + pub fn prev_diff(&mut self) { + self.difficulty.prev(); + } + pub fn next_diff(&mut self) { + self.difficulty.next(); + } + pub fn first_diff(&mut self) { + self.difficulty.first(); + } + pub fn last_diff(&mut self) { + self.difficulty.last(); + } +} + +impl<'tab2> TopicTagsQS<'tab2> { + pub async fn new() -> TopicTagsQS<'tab2> { + let (new_index, topic_tags, ac_status) = Self::base_info().await; + + Self { + topic: topics::TopicsState::new(topic_tags), + + question_state: question::TopicQuestionState::new(new_index), + + sync_bar: sync_bar::BarState::default(), + + index: Tab2Panel::AllTopics, + + inputline: intputline::InputLine::default(), + + difficulty: diff::DiffState::new( + ac_status + .iter() + .map(|v| v.0.clone()) + .collect(), + ), + + ac_status, + } + } + + /// return `new_index`, `topic_tags`, `ac_status` + pub async fn base_info() -> ( + Box<[new_index::Model]>, + Box<[topic_tags::Model]>, + Box<[(String, u32, u32)]>, + ) { + let (all_qs_res, topic_res, status) = tokio::join!( + query::Query::query_all_new_index(None), + query::Query::query_all_topic(), + query::Query::query_status() + ); + ( + all_qs_res.unwrap_or_default().into(), + topic_res.unwrap_or_default().into(), + status.unwrap_or_default().into(), + ) + } + + pub fn update_percent(&mut self, cur_perc: f64) { + self.sync_bar.update(cur_perc); + } + /// refresh `filtered_qs` + pub fn refresh_filter_by_input(&mut self) { + self.question_state.filtered_qs = self + .question_state + .all_qs + .par_iter() + .filter(|&v| filter(self.inputline.first_line(), &v.to_string())) + .cloned() + .collect(); + } + /// refresh `all_qs` + pub async fn refresh_filter_by_topic_diff(&mut self) { + if self.topic.user_topic_tags.is_empty() { + self.question_state.all_qs = + Query::query_all_new_index(Some(self.difficulty.user_diff.clone())) + .await + .unwrap_or_default() + .into(); + } + else { + let diff = self.difficulty.user_diff.clone(); + self.question_state.all_qs = + Query::query_by_topic(&self.topic.user_topic_tags, Some(diff)) + .await + .unwrap_or_default() + .into(); + } + } +} + +// all topic tags, add remove topic +impl<'tab2> TopicTagsQS<'tab2> { + /// remove a topic and refresh question + pub async fn rm_user_topic(&mut self) -> bool { + let trigger = self.topic.rm_user_topic(); + if trigger { + self.refresh_filter_by_topic_diff() + .await; + self.refresh_filter_by_input(); + } + trigger + } + + /// return need refresh or not + pub async fn add_user_topic(&mut self) -> bool { + let trigger = self.topic.add_user_topic(); + if trigger { + self.refresh_filter_by_topic_diff() + .await; + self.refresh_filter_by_input(); + } + trigger + } + + // topic_tags ////////////////////////////////// + pub fn first_topic(&mut self) { + self.topic.first(); + } + pub fn last_topic(&mut self) { + self.topic.last(); + } + pub fn next_topic(&mut self) { + self.topic.next(); + } + pub fn prev_topic(&mut self) { + self.topic.prev(); + } +} + +// filtered questions +impl<'tab2> TopicTagsQS<'tab2> { + pub fn next_qs(&mut self) { + self.question_state.next(); + } + pub fn prev_qs(&mut self) { + self.question_state.prev(); + } + pub fn first_qs(&mut self) { + self.question_state + .filtered_topic_qs_state + .select(Some(0)); + } + pub fn last_qs(&mut self) { + self.question_state.last(); + } + pub fn cur_qs_slug(&self) -> Option { + self.question_state.cur_qs_slug() + } +} + +// user topic tags +impl<'tab2> TopicTagsQS<'tab2> { + pub fn prev_user_topic(&mut self) { + self.topic.prev_user(); + } + + pub fn next_user_topic(&mut self) { + self.topic.next_user(); + } + pub fn last_user_topic(&mut self) { + self.topic.last_user(); + } + pub fn first_user_topic(&mut self) { + self.topic.first_user(); + } +} diff --git a/crates/lcode/src/mytui/mod.rs b/crates/lcode/src/mytui/mod.rs index 5c09466..5fe77b9 100644 --- a/crates/lcode/src/mytui/mod.rs +++ b/crates/lcode/src/mytui/mod.rs @@ -31,7 +31,7 @@ pub async fn run() -> Result<()> { }, UserEvent::UserInfo(info) => app.get_status_done(*info), UserEvent::SubmitDone(s_res) => { - // update infos + // update info if s_res.total_correct == s_res.total_testcases { app.user_info_and_checkin(); } diff --git a/crates/lcode/src/mytui/ui/edit_ui.rs b/crates/lcode/src/mytui/ui/edit_ui.rs index 7241cf0..492303f 100644 --- a/crates/lcode/src/mytui/ui/edit_ui.rs +++ b/crates/lcode/src/mytui/ui/edit_ui.rs @@ -34,12 +34,13 @@ pub fn draw_qs_content(f: &mut Frame, app: &mut App, area: Rect) { .unwrap_or(&app.cur_qs.title) }; - let text = app.cur_qs.to_tui_vec(); + let text = app.cur_qs.to_para_vec(); - app.edit.vertical_row_len = text.len(); - app.edit.content_vert_scroll_state = app + app.edit.qs_content.content_row_num = text.len(); + app.edit.qs_content.vert_scroll_state = app .edit - .content_vert_scroll_state + .qs_content + .vert_scroll_state .content_length(text.len()); let paragraph = Paragraph::new(text) @@ -50,29 +51,26 @@ pub fn draw_qs_content(f: &mut Frame, app: &mut App, area: Rect) { )) .style(G_THEME.edit.content_border) .alignment(Alignment::Left) - .wrap(Wrap { trim: true }) - .scroll((app.edit.content_vert_scroll as u16, 0)); + .wrap(Wrap { trim: false }) + .scroll((app.edit.qs_content.vert_scroll as u16, 0)); f.render_widget(paragraph, area); - f.render_stateful_widget( - Scrollbar::default() - .orientation(ScrollbarOrientation::VerticalRight) - .begin_symbol(Some("↑")) - .end_symbol(Some("↓")), - area, - &mut app.edit.content_vert_scroll_state, - ); + let scrollbar = Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .begin_symbol(Some("↑")) + .end_symbol(Some("↓")); + f.render_stateful_widget(scrollbar, area, &mut app.edit.qs_content.vert_scroll_state); } /// for edit code pub fn draw_code_block(f: &mut Frame, app: &mut App, area: Rect) { - let title = match app.edit.code_block_mode { + let title = match app.edit.code_block.mode { TuiMode::Normal => "Normal, Press q exit edit, vim like keybind, ctrl-s save", TuiMode::Insert => "Insert, emacs like keybind", TuiMode::OutEdit => "OutEdit, Press e to start edit 🖊️", TuiMode::Visual => todo!(), }; - let blk = if matches!(app.edit.code_block_mode, TuiMode::OutEdit) { + let blk = if matches!(app.edit.code_block.mode, TuiMode::OutEdit) { Block::default() } else { @@ -80,12 +78,16 @@ pub fn draw_code_block(f: &mut Frame, app: &mut App, area: Rect) { } .title(title) .borders(Borders::ALL); - app.edit.code_block.set_block(blk); app.edit + .code_block + .code_block + .set_block(blk); + app.edit + .code_block .code_block .set_cursor_style(G_THEME.edit.code_block_cursor); - f.render_widget(app.edit.code_block.widget(), area); + f.render_widget(app.edit.code_block.code_block.widget(), area); } pub fn draw_pop_buttons(f: &mut Frame, app: &App, area: Rect) { @@ -105,24 +107,24 @@ pub fn draw_pop_buttons(f: &mut Frame, app: &App, area: Rect) { f.render_widget( Button::new("Test Code 🍨") .theme(Theme::test_color()) - .state(app.edit.button_state.states[0]), + .state(app.edit.button.button_state.states[0]), test, ); f.render_widget(Clear, submit); f.render_widget( Button::new("Submit Code 🚩") .theme(Theme::blue()) - .state(app.edit.button_state.states[1]), + .state(app.edit.button.button_state.states[1]), submit, ); } pub fn draw_pop_submit(f: &mut Frame, app: &mut App, area: Rect) { - let res = &app.edit.submit_res; + let res = &app.edit.submit.result; let status_msg = res.start_tui_text(); - let para = Paragraph::new(status_msg).scroll((0, app.edit.submit_hori_scroll as u16)); + let para = Paragraph::new(status_msg).scroll((0, app.edit.submit.hori_scroll as u16)); let area = centered_rect_percent(60, 60, area); f.render_widget(Clear, area); @@ -149,9 +151,9 @@ pub fn draw_pop_submit(f: &mut Frame, app: &mut App, area: Rect) { f.render_widget(para, head); - #[cfg(debug_assertions)] - let ratio: f64 = res.runtime_percentile().max(80.0); - #[cfg(not(debug_assertions))] + // #[cfg(debug_assertions)] + // let ratio: f64 = res.runtime_percentile().max(80.0); + // #[cfg(not(debug_assertions))] let ratio = res.runtime_percentile(); let gauge_fast = Gauge::default() .label( @@ -165,9 +167,9 @@ pub fn draw_pop_submit(f: &mut Frame, app: &mut App, area: Rect) { .gauge_style(G_THEME.edit.gauge_time); f.render_widget(gauge_fast, helper::nested_rect(runtime, 2, 2, 0, 0)); - #[cfg(debug_assertions)] - let ratio: f64 = res.memory_percentile().max(60.0); - #[cfg(not(debug_assertions))] + // #[cfg(debug_assertions)] + // let ratio: f64 = res.memory_percentile().max(60.0); + // #[cfg(not(debug_assertions))] let ratio = res.memory_percentile(); let gauge_mem = Gauge::default() .ratio((ratio / 100.0).min(1.0)) @@ -182,9 +184,9 @@ pub fn draw_pop_submit(f: &mut Frame, app: &mut App, area: Rect) { f.render_widget(gauge_mem, helper::nested_rect(memory, 2, 2, 0, 0)); let (t_corr, t_case) = (res.total_correct(), res.total_testcases()); - #[cfg(debug_assertions)] - let ratio: f64 = (t_corr as u32 as f64 / t_case.max(1) as u32 as f64).max(0.3); - #[cfg(not(debug_assertions))] + // #[cfg(debug_assertions)] + // let ratio: f64 = (t_corr as u32 as f64 / t_case.max(1) as u32 as f64).max(0.3); + // #[cfg(not(debug_assertions))] let ratio = t_corr as u32 as f64 / t_case.max(1) as u32 as f64; let gauge_test_case = Gauge::default() .label( @@ -197,18 +199,18 @@ pub fn draw_pop_submit(f: &mut Frame, app: &mut App, area: Rect) { let other_msg = res.end_tui_text(); - app.edit.submit_row_len = other_msg.len(); + app.edit.submit.row_len = other_msg.len(); let para = Paragraph::new(other_msg).scroll(( - app.edit.submit_vert_scroll as u16, - app.edit.submit_hori_scroll as u16, + app.edit.submit.vert_scroll as u16, + app.edit.submit.hori_scroll as u16, )); f.render_widget(para, other); } pub fn draw_pop_test(f: &mut Frame, app: &mut App, area: Rect) { - let text = app.edit.test_res.to_tui_vec(); - app.edit.test_row_len = text.len(); + let text = app.edit.test.result.to_para_vec(); + app.edit.test.row_len = text.len(); let para = Paragraph::new(text) .block( helper::title_block(Line::from(vec![ @@ -218,8 +220,8 @@ pub fn draw_pop_test(f: &mut Frame, app: &mut App, area: Rect) { .border_style(G_THEME.edit.test_border), ) .scroll(( - app.edit.test_vert_scroll as u16, - app.edit.test_hori_scroll as u16, + app.edit.test.vert_scroll as u16, + app.edit.test.hori_scroll as u16, )); let area = centered_rect_percent(60, 60, area); diff --git a/crates/lcode/src/mytui/ui/filter_topic.rs b/crates/lcode/src/mytui/ui/filter_topic.rs index aa86052..07da740 100644 --- a/crates/lcode/src/mytui/ui/filter_topic.rs +++ b/crates/lcode/src/mytui/ui/filter_topic.rs @@ -16,7 +16,8 @@ use crate::{ pub fn draw_difficults(f: &mut Frame, app: &mut App, area: Rect) { let items = app .topic - .difficultys + .difficulty + .difficulties .iter() .map(|v| ListItem::new(v.as_str())); @@ -33,17 +34,22 @@ pub fn draw_difficults(f: &mut Frame, app: &mut App, area: Rect) { .border_style(style) .borders(Borders::ALL) .title( - if app.topic.user_diff.is_empty() { + if app + .topic + .difficulty + .user_diff + .is_empty() + { "Difficulty" } else { - &app.topic.user_diff + &app.topic.difficulty.user_diff }, ) .title_alignment(Alignment::Center), ) .highlight_style(G_THEME.topic.list_highlight); - f.render_stateful_widget(list, area, &mut app.topic.difficultys_state); + f.render_stateful_widget(list, area, &mut app.topic.difficulty.diff_list_state); } // pub fn draw_chart(f: &mut Frame, app: &App, area: Rect) { // unimplemented!() @@ -87,22 +93,27 @@ pub fn draw_status(f: &mut Frame, app: &App, area: Rect) { f.render_widget(total, chunk[3]); } pub fn draw_all_topic_tags(f: &mut Frame, app: &mut App, area: Rect) { - let items = app.topic.topic_tags.iter().map(|v| { - let name = if G_USER_CONFIG.config.translate { - let mut name = v - .name_translated - .as_deref() - .unwrap_or_default(); - if name.is_empty() { - name = v.name.as_str(); + let items = app + .topic + .topic + .topic_tags + .iter() + .map(|v| { + let name = if G_USER_CONFIG.config.translate { + let mut name = v + .name_translated + .as_deref() + .unwrap_or_default(); + if name.is_empty() { + name = v.name.as_str(); + } + name } - name - } - else { - v.name.as_str() - }; - ListItem::new(name) - }); + else { + v.name.as_str() + }; + ListItem::new(name) + }); let style = if app.topic.index == Tab2Panel::AllTopics { G_THEME.topic.active_border } @@ -119,13 +130,14 @@ pub fn draw_all_topic_tags(f: &mut Frame, app: &mut App, area: Rect) { ) .highlight_style(G_THEME.topic.list_highlight); // .highlight_symbol(">>"); - f.render_stateful_widget(list, area, &mut app.topic.topic_tags_state); + f.render_stateful_widget(list, area, &mut app.topic.topic.topic_tags_state); } pub fn draw_user_topic(f: &mut Frame, app: &mut App, area: Rect) { let items: Box>> = if G_USER_CONFIG.config.translate { Box::new( app.topic + .topic .user_topic_tags_translated .iter() .map(|v| ListItem::new(v.as_str())), @@ -134,6 +146,7 @@ pub fn draw_user_topic(f: &mut Frame, app: &mut App, area: Rect) { else { Box::new( app.topic + .topic .user_topic_tags .iter() .map(|v| ListItem::new(v.as_str())), @@ -156,12 +169,13 @@ pub fn draw_user_topic(f: &mut Frame, app: &mut App, area: Rect) { ) .highlight_style(G_THEME.topic.list_highlight); // .highlight_symbol(">>"); - f.render_stateful_widget(list, area, &mut app.topic.user_topic_tags_state); + f.render_stateful_widget(list, area, &mut app.topic.topic.user_topic_tags_state); } pub fn draw_filtered_qs(f: &mut Frame, app: &mut App, area: Rect) { let items: Vec = app .topic + .question_state .filtered_qs .par_iter() .map(|v| ListItem::new(v.to_string())) @@ -184,13 +198,20 @@ pub fn draw_filtered_qs(f: &mut Frame, app: &mut App, area: Rect) { ) .highlight_style(G_THEME.topic.list_highlight); // .highlight_symbol(">>"); - f.render_stateful_widget(list, area, &mut app.topic.filtered_topic_qs_state); + f.render_stateful_widget( + list, + area, + &mut app + .topic + .question_state + .filtered_topic_qs_state, + ); } /// progress bar, it will draw in `area` bottom pub fn draw_sync_progress_new(f: &mut Frame, app: &App, area: Rect) { let label = Span::styled( - format!("{:.2}%", app.topic.cur_perc * 100.0), + format!("{:.2}%", app.topic.sync_bar.cur_perc * 100.0), G_THEME.topic.label, ); let gauge = Gauge::default() @@ -201,7 +222,7 @@ pub fn draw_sync_progress_new(f: &mut Frame, app: &App, area: Rect) { ) .gauge_style(G_THEME.topic.gauge) .label(label) - .ratio(app.topic.cur_perc); + .ratio(app.topic.sync_bar.cur_perc); // let area = centered_rect(60, 20, area); let area = bottom_rect(60, area); @@ -212,7 +233,7 @@ pub fn draw_sync_progress_new(f: &mut Frame, app: &App, area: Rect) { /// input to filter question pub fn draw_input_line(f: &mut Frame, app: &mut App, area: Rect) { - let (title, sty) = match app.topic.input_line_mode { + let (title, sty) = match app.topic.inputline.mode { TuiMode::Normal => { unreachable!() }, @@ -226,11 +247,11 @@ pub fn draw_input_line(f: &mut Frame, app: &mut App, area: Rect) { G_THEME.topic.text_line_outedit, ), }; - app.topic.text_line.set_block( + app.topic.inputline.text_line.set_block( Block::default() .borders(Borders::ALL) .set_style(sty) .title(title), ); - f.render_widget(app.topic.text_line.widget(), area); + f.render_widget(app.topic.inputline.text_line.widget(), area); } diff --git a/crates/lcode/src/mytui/ui/infos.rs b/crates/lcode/src/mytui/ui/info.rs similarity index 87% rename from crates/lcode/src/mytui/ui/infos.rs rename to crates/lcode/src/mytui/ui/info.rs index 7131cbe..cb243bb 100644 --- a/crates/lcode/src/mytui/ui/infos.rs +++ b/crates/lcode/src/mytui/ui/info.rs @@ -3,17 +3,17 @@ use ratatui::{prelude::*, widgets::*}; use crate::app::inner::App; -pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { +pub fn draw_info(f: &mut Frame, app: &mut App, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Min(11), - Constraint::Max(app.infos.keymaps_items.len() as u16 + 3), + Constraint::Max(app.info.keymap.keymaps_items.len() as u16 + 3), ]) .split(area); assert!(chunks.len() >= 2); - let info = &app.infos.user_status; + let info = &app.info.user_status; let name = format!( "👤 User Name: {}", @@ -21,7 +21,7 @@ pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { .as_deref() .unwrap_or("unknown") ); - let points = format!("🌟 Points: {} 🪙", app.infos.points.points()); + let points = format!("🌟 Points: {} 🪙", app.info.points.points()); let mut items = Vec::with_capacity(9); items.push(name); @@ -52,9 +52,9 @@ pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { items.push(points); let pass_data = app - .infos + .info .pass_data - .infos() + .info() .into_iter() .map(ListItem::new); let pass_data = vec![ListItem::new("🐾 Pass Info")] @@ -64,17 +64,17 @@ pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { Block::default() .borders(Borders::ALL) .title_alignment(Alignment::Center) - .title("Pass Infos"), + .title("Pass Info"), ); let user_info_list = List::new(items.into_iter().map(ListItem::new)).block( Block::default() .borders(Borders::ALL) .title_alignment(Alignment::Center) - .title("User Infos"), + .title("User Info"), ); - let keymap_list = List::new(app.infos.keymaps_items.clone()) + let keymap_list = List::new(app.info.keymap.keymaps_items.clone()) .block( Block::default() .borders(Borders::ALL) @@ -91,7 +91,7 @@ pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { assert!(chunks1.len() >= 2); f.render_widget(user_info_list, chunks1[0]); f.render_widget(pass_info_list, chunks1[1]); - f.render_stateful_widget(keymap_list, chunks[1], &mut app.infos.keymaps_state); + f.render_stateful_widget(keymap_list, chunks[1], &mut app.info.keymap.keymaps_state); } // pub fn draw_avatar( @@ -102,7 +102,7 @@ pub fn draw_infos(f: &mut Frame, app: &mut App, area: Rect) { // let mut picker = Picker::from_termios()?; // picker.guess_protocol(); // picker.background_color = Some(image::Rgb::([255, 0, 255])); -// let dyn_img = Reader::open(app.infos.avatar_path.as_path())?.decode()?; +// let dyn_img = Reader::open(app.info.avatar_path.as_path())?.decode()?; // // let mut image_state = picker.new_resize_protocol(dyn_img); // diff --git a/crates/lcode/src/mytui/ui/mod.rs b/crates/lcode/src/mytui/ui/mod.rs index 3274338..4d248da 100644 --- a/crates/lcode/src/mytui/ui/mod.rs +++ b/crates/lcode/src/mytui/ui/mod.rs @@ -1,6 +1,6 @@ mod edit_ui; mod filter_topic; -mod infos; +mod info; mod select_ui; use lcode_config::global::G_THEME; @@ -38,10 +38,15 @@ pub(super) fn start_ui(f: &mut Frame, app: &mut App) { select_ui::draw_table(f, app, chunks[2]); - if app.select.all_questions.is_empty() { + if app + .select + .qs_state + .all_questions + .is_empty() + { select_ui::draw_pop_msg(f, f.size()); } - if app.select.sync_state { + if app.select.sync_bar.show { select_ui::draw_sync_progress(f, app, f.size()); } }, @@ -56,15 +61,14 @@ pub(super) fn start_ui(f: &mut Frame, app: &mut App) { edit_ui::draw_qs_content(f, app, chunks1[0]); edit_ui::draw_code_block(f, app, chunks1[1]); - if app.edit.show_pop_menu { + if app.edit.button.show { edit_ui::draw_pop_buttons(f, app, f.size()); } - if app.edit.show_submit_res { - // edit_ui::draw_pop_submit(f, app, f.size()); + if app.edit.submit.show { edit_ui::draw_pop_submit(f, app, f.size()); } - if app.edit.show_test_res { + if app.edit.test.show { edit_ui::draw_pop_test(f, app, f.size()); } if app.save_code { @@ -108,14 +112,14 @@ pub(super) fn start_ui(f: &mut Frame, app: &mut App) { filter_topic::draw_filtered_qs(f, app, qs_area[1]); filter_topic::draw_input_line(f, app, qs_area[0]); - if app.topic.topic_tags.is_empty() { + if app.topic.topic.topic_tags.is_empty() { select_ui::draw_pop_msg(f, f.size()); } - if app.topic.sync_state { + if app.topic.sync_bar.show { filter_topic::draw_sync_progress_new(f, app, f.size()); } }, - TuiIndex::Infos => infos::draw_infos(f, app, chunks[1]), + TuiIndex::Info => info::draw_info(f, app, chunks[1]), // 4 => show_config::draw_config(f, app, chunks[1]), }; diff --git a/crates/lcode/src/mytui/ui/select_ui.rs b/crates/lcode/src/mytui/ui/select_ui.rs index 1d6f499..be60a82 100644 --- a/crates/lcode/src/mytui/ui/select_ui.rs +++ b/crates/lcode/src/mytui/ui/select_ui.rs @@ -12,7 +12,7 @@ use crate::{ /// some info pub fn draw_msg(f: &mut Frame, app: &mut App, area: Rect) { - let (msg, style) = match app.select.input_line_mode { + let (msg, style) = match app.select.inputline.mode { TuiMode::Insert => ( vec![ "Default press ".into(), @@ -44,27 +44,31 @@ pub fn draw_msg(f: &mut Frame, app: &mut App, area: Rect) { /// input to filter question pub fn draw_input_line(f: &mut Frame, app: &mut App, area: Rect) { - let (title, sty) = match app.select.input_line_mode { + let (title, sty) = match app.select.inputline.mode { TuiMode::Normal => todo!(), TuiMode::Insert => ("Input to filter", G_THEME.select.text_line_insert), TuiMode::Visual => todo!(), TuiMode::OutEdit => ("Input to filter", G_THEME.select.text_line_outedit), }; - app.select.text_line.set_block( - Block::default() - .borders(Borders::ALL) - .set_style(sty) - .title(title), - ); + app.select + .inputline + .text_line + .set_block( + Block::default() + .borders(Borders::ALL) + .set_style(sty) + .title(title), + ); - f.render_widget(app.select.text_line.widget(), area); + f.render_widget(app.select.inputline.text_line.widget(), area); } /// list questions pub fn draw_table(f: &mut Frame, app: &mut App, area: Rect) { let items: Vec> = app .select + .qs_state .filtered_qs .par_iter() .map(|v| -> Row<'_> { @@ -126,18 +130,18 @@ pub fn draw_table(f: &mut Frame, app: &mut App, area: Rect) { .block( Block::default() .borders(Borders::ALL) - .title(format!("Sum: {}", app.select.filtered_qs.len())), + .title(format!("Sum: {}", app.select.qs_state.filtered_qs.len())), ) .highlight_style(G_THEME.select.highlight_style) .highlight_symbol(""); - f.render_stateful_widget(items, area, &mut app.select.state); + f.render_stateful_widget(items, area, &mut app.select.qs_state.state); } /// progress bar, it will draw in `area` bottom pub fn draw_sync_progress(f: &mut Frame, app: &mut App, area: Rect) { let label = Span::styled( - format!("{:.2}%", app.select.cur_perc * 100.0), + format!("{:.2}%", app.select.sync_bar.cur_perc * 100.0), G_THEME.select.label, ); let gauge = Gauge::default() @@ -148,7 +152,7 @@ pub fn draw_sync_progress(f: &mut Frame, app: &mut App, area: Rect) { ) .gauge_style(G_THEME.select.gauge) .label(label) - .ratio(app.select.cur_perc); + .ratio(app.select.sync_bar.cur_perc); // let area = centered_rect(60, 20, area); let area = bottom_rect(60, area); diff --git a/crates/leetcode-api/Cargo.toml b/crates/leetcode-api/Cargo.toml index 33c9b9a..28305f5 100644 --- a/crates/leetcode-api/Cargo.toml +++ b/crates/leetcode-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leetcode-api" -version = "0.3.16" +version = "0.4.0" description = "leetcode api" license = "MIT" edition = { workspace = true } diff --git a/crates/leetcode-api/src/leetcode/impl_lc/user_info.rs b/crates/leetcode-api/src/leetcode/impl_lc/user_info.rs index 8f1288b..6119d3a 100644 --- a/crates/leetcode-api/src/leetcode/impl_lc/user_info.rs +++ b/crates/leetcode-api/src/leetcode/impl_lc/user_info.rs @@ -24,7 +24,7 @@ use crate::{ Json, }; -// some infos +// some info impl LeetCode { /// download user avatar image pub async fn dow_user_avator(&self, status: &UserStatus) -> PathBuf { diff --git a/crates/leetcode-api/src/leetcode/resps/pass_qs.rs b/crates/leetcode-api/src/leetcode/resps/pass_qs.rs index c04c3b7..e05be60 100644 --- a/crates/leetcode-api/src/leetcode/resps/pass_qs.rs +++ b/crates/leetcode-api/src/leetcode/resps/pass_qs.rs @@ -24,7 +24,7 @@ pub struct DataCom { } impl DataCom { - fn infos(&self) -> Vec { + fn info(&self) -> Vec { let mut res = Vec::with_capacity(4); if let Some(v) = &self.matched_user.submit_stats { for i in &v.ac_submission_num { @@ -66,12 +66,12 @@ pub enum PassData { } impl PassData { - pub fn infos(&self) -> Vec { + pub fn info(&self) -> Vec { match self { Self::Cn(v) => v .user_profile_user_question_progress - .infos(), - Self::Com(v) => v.infos(), + .info(), + Self::Com(v) => v.info(), } } } @@ -137,7 +137,7 @@ pub struct UserProfileUserQuestionProgress { } impl UserProfileUserQuestionProgress { - fn infos(&self) -> Vec { + fn info(&self) -> Vec { let mut res = Vec::with_capacity(4); let mut all = 0; for i in &self.num_accepted_questions { diff --git a/crates/leetcode-api/src/render/mod.rs b/crates/leetcode-api/src/render/mod.rs index e311986..29968c2 100644 --- a/crates/leetcode-api/src/render/mod.rs +++ b/crates/leetcode-api/src/render/mod.rs @@ -41,7 +41,7 @@ pub trait Render { /// for ratatui's paragraph widget #[cfg(feature = "ratatui")] - fn to_tui_vec(&self) -> Vec; + fn to_para_vec(&self) -> Vec; /// use [`mdcat`](https://github.com/swsnr/mdcat/) render question content fn render_with_mdcat(&self) { diff --git a/crates/leetcode-api/src/render/qs_detail.rs b/crates/leetcode-api/src/render/qs_detail.rs index 135fbab..e74ec19 100644 --- a/crates/leetcode-api/src/render/qs_detail.rs +++ b/crates/leetcode-api/src/render/qs_detail.rs @@ -58,7 +58,7 @@ impl Render for Question { } #[cfg(feature = "ratatui")] - fn to_tui_vec(&self) -> Vec { + fn to_para_vec(&self) -> Vec { use scraper::Html; let content = if G_USER_CONFIG.config.translate { diff --git a/crates/leetcode-api/src/render/run_res.rs b/crates/leetcode-api/src/render/run_res.rs index a0e8bc0..8580b46 100644 --- a/crates/leetcode-api/src/render/run_res.rs +++ b/crates/leetcode-api/src/render/run_res.rs @@ -234,7 +234,7 @@ impl Render for RunResult { } #[cfg(feature = "ratatui")] - fn to_tui_vec(&self) -> Vec { + fn to_para_vec(&self) -> Vec { let total_testcases = self.total_testcases(); let total_correct = self.total_correct(); diff --git a/crates/leetcode-api/tests/lc_manual.rs b/crates/leetcode-api/tests/lc_manual.rs index 81cdfbc..e0ff54b 100644 --- a/crates/leetcode-api/tests/lc_manual.rs +++ b/crates/leetcode-api/tests/lc_manual.rs @@ -39,7 +39,7 @@ async fn submit_work() { .submit_code(IdSlug::Id(27)) .await .unwrap(); - dbg!(res.to_tui_vec()); + dbg!(res.to_para_vec()); println!(r##"(| res |) -> {} "##, res.to_md_str(false)); res.render_with_mdcat(); }