Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: chaozhouzhang/vscode-leetcode
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: LeetCode-OpenSource/vscode-leetcode
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Jan 12, 2020

  1. Copy the full SHA
    c20a2d5 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9017eda View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6efb683 View commit details

Commits on Feb 13, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1c4a39e View commit details

Commits on Feb 14, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1a129db View commit details

Commits on Feb 22, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d7e4c10 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6358921 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d160d1f View commit details

Commits on Feb 23, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ac9df4d View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    15c4216 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bedd693 View commit details

Commits on Apr 18, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1835d07 View commit details

Commits on May 20, 2020

  1. Update third party notice

    jdneo committed May 20, 2020
    Copy the full SHA
    0828766 View commit details

Commits on May 28, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9bf5877 View commit details

Commits on Jun 2, 2020

  1. Update README_zh-CN.md (LeetCode-OpenSource#566)

    * Update README_zh-CN.md
    
    * Update README_zh-CN.md
    yihong0618 authored Jun 2, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    672de39 View commit details
  2. Update README.md (LeetCode-OpenSource#565)

    * Update README.md
    
    * Update README.md
    yihong0618 authored Jun 2, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a11fb1d View commit details

Commits on Jun 4, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d1307f1 View commit details

Commits on Jun 27, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7705ce7 View commit details
  2. Prepare for 0.17.0 (LeetCode-OpenSource#586)

    * Prepare for 0.17.0
    
    * Update marketplace URL
    jdneo authored Jun 27, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7505180 View commit details

Commits on Jun 29, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7aeb97e View commit details

Commits on Jul 2, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7bc6d6c View commit details

Commits on Jul 21, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ade48fb View commit details
  2. chore(deps): bump lodash from 4.17.13 to 4.17.19 (LeetCode-OpenSource…

    …#597)
    
    Bumps [lodash](https://github.com/lodash/lodash) from 4.17.13 to 4.17.19.
    - [Release notes](https://github.com/lodash/lodash/releases)
    - [Commits](lodash/lodash@4.17.13...4.17.19)
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jul 21, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5fa449e View commit details

Commits on Jul 23, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1893d34 View commit details

Commits on Dec 11, 2020

  1. chore(deps): bump ini from 1.3.5 to 1.3.7 (LeetCode-OpenSource#656)

    Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
    - [Release notes](https://github.com/isaacs/ini/releases)
    - [Commits](npm/ini@v1.3.5...v1.3.7)
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Dec 11, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4c0d59a View commit details

Commits on May 31, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    172c878 View commit details
  2. chore: Fix typo (LeetCode-OpenSource#694)

    Co-authored-by: Sheng Chen <sheche@microsoft.com>
    tejasvi and jdneo authored May 31, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6b95d5c View commit details
  3. feat: Added an option to disable endpoint translation (LeetCode-OpenS…

    …ource#690)
    
    Co-authored-by: Sheng Chen <sheche@microsoft.com>
    dzz007 and jdneo authored May 31, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0fcf9f1 View commit details

Commits on Jun 3, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ff46c33 View commit details
  2. docs: Update README.md (LeetCode-OpenSource#637)

    Co-authored-by: Sheng Chen <sheche@microsoft.com>
    Peng-Yu Chen and jdneo authored Jun 3, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ce92866 View commit details

Commits on Jun 8, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    45ae245 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    802dc40 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    adf255c View commit details

Commits on Aug 20, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e93bb6e View commit details

Commits on Aug 21, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    de1dc41 View commit details

Commits on May 9, 2022

  1. fix: update cn domain (LeetCode-OpenSource#800)

    * fix: update cn domain
    
    * fix(ci): upgrade node to 14
    
    * chore: bump cli
    Miloas authored May 9, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b7638bb View commit details
  2. 0.18.1

    Miloas committed May 9, 2022

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    8d191f9 View commit details
  3. chore(deps): bump ansi-regex from 3.0.0 to 3.0.1 (LeetCode-OpenSource…

    …#805)
    
    Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 3.0.0 to 3.0.1.
    - [Release notes](https://github.com/chalk/ansi-regex/releases)
    - [Commits](chalk/ansi-regex@v3.0.0...v3.0.1)
    
    ---
    updated-dependencies:
    - dependency-name: ansi-regex
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 9, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4f8c777 View commit details
  4. chore(deps): bump minimist from 1.2.5 to 1.2.6 (LeetCode-OpenSource#788)

    Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
    - [Release notes](https://github.com/substack/minimist/releases)
    - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)
    
    ---
    updated-dependencies:
    - dependency-name: minimist
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 9, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a68f935 View commit details
  5. chore(deps): bump i from 0.3.6 to 0.3.7 (LeetCode-OpenSource#743)

    Bumps [i](https://github.com/pksunkara/inflect) from 0.3.6 to 0.3.7.
    - [Release notes](https://github.com/pksunkara/inflect/releases)
    - [Commits](pksunkara/inflect@v0.3.6...v0.3.7)
    
    ---
    updated-dependencies:
    - dependency-name: i
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 9, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2afb906 View commit details
  6. chore(deps): bump moment from 2.29.1 to 2.29.3 (LeetCode-OpenSource#806)

    Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.3.
    - [Release notes](https://github.com/moment/moment/releases)
    - [Changelog](https://github.com/moment/moment/blob/2.29.3/CHANGELOG.md)
    - [Commits](moment/moment@2.29.1...2.29.3)
    
    ---
    updated-dependencies:
    - dependency-name: moment
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 9, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d713ad5 View commit details

Commits on May 15, 2024

  1. feat: change login way and add tracking logic option (LeetCode-OpenSo…

    …urce#944)
    
    fix: resolve UAT issues
    
    fix: update action
    
    feat: add login progress
    
    fix: change function name
    
    Co-authored-by: leo.zhao<zale0201@gmail.com>
    leozhao21 authored May 15, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    71264fa View commit details

Commits on Jul 26, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    b495812 View commit details

Commits on Jul 30, 2024

  1. feat: re-add cookie-based login method (LeetCode-OpenSource#969)

    * feat: re-add cookie-based login method
    
    * chore: optimize tslint config
    
    * chore : change build.yml
    
    ---------
    
    Co-authored-by: leo.zhao <zale0201@gmail.com>
    tomoyachen and leozhao21 authored Jul 30, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    bbc041d View commit details
  2. Copy the full SHA
    67101c9 View commit details
  3. chore: add change log

    leozhao21 committed Jul 30, 2024
    Copy the full SHA
    6f42925 View commit details

Commits on Sep 13, 2024

  1. Copy the full SHA
    586b3e4 View commit details
Showing with 3,998 additions and 1,352 deletions.
  1. +3 −3 .github/ISSUE_TEMPLATE/question.md
  2. +50 −0 .github/workflows/build.yml
  3. +0 −19 .travis.yml
  4. +6 −3 .vscode/settings.json
  5. +0 −1 .vscodeignore
  6. +19 −19 ACKNOWLEDGEMENTS.md
  7. +128 −74 CHANGELOG.md
  8. +2 −1 LICENSE
  9. +61 −52 README.md
  10. +60 −52 docs/README_zh-CN.md
  11. +2,591 −753 package-lock.json
  12. +112 −64 package.json
  13. BIN resources/LeetCode.png
  14. +5 −7 resources/LeetCode.svg
  15. BIN resources/check.png
  16. BIN resources/dark/dislike.png
  17. +0 −3 resources/dark/endpoint.svg
  18. BIN resources/dark/like.png
  19. +0 −4 resources/dark/refresh.svg
  20. +0 −3 resources/dark/search.svg
  21. +0 −3 resources/dark/signin.svg
  22. BIN resources/light/dislike.png
  23. +0 −3 resources/light/endpoint.svg
  24. BIN resources/light/like.png
  25. +0 −4 resources/light/refresh.svg
  26. +0 −3 resources/light/search.svg
  27. +0 −3 resources/light/signin.svg
  28. BIN resources/lock.png
  29. BIN resources/x.png
  30. +5 −21 src/codelens/CodeLensController.ts
  31. +18 −1 src/codelens/CustomCodeLensProvider.ts
  32. +4 −4 src/commands/list.ts
  33. +36 −2 src/commands/plugin.ts
  34. +79 −49 src/commands/show.ts
  35. +10 −2 src/commands/star.ts
  36. +14 −1 src/explorer/LeetCodeNode.ts
  37. +28 −16 src/explorer/LeetCodeTreeDataProvider.ts
  38. +40 −0 src/explorer/LeetCodeTreeItemDecorationProvider.ts
  39. +16 −4 src/explorer/explorerNodeManager.ts
  40. +39 −5 src/extension.ts
  41. +55 −0 src/globalState.ts
  42. +69 −29 src/leetCodeExecutor.ts
  43. +129 −62 src/leetCodeManager.ts
  44. +25 −0 src/request/query-user-data.ts
  45. +63 −5 src/shared.ts
  46. +3 −3 src/utils/cpUtils.ts
  47. +28 −0 src/utils/httpUtils.ts
  48. +48 −0 src/utils/settingUtils.ts
  49. +26 −0 src/utils/toolUtils.ts
  50. +130 −0 src/utils/trackingUtils.ts
  51. +1 −1 src/utils/uiUtils.ts
  52. +10 −4 src/utils/workspaceUtils.ts
  53. +4 −1 src/utils/wslUtils.ts
  54. +34 −40 src/webview/leetCodePreviewProvider.ts
  55. +0 −1 src/webview/leetCodeSolutionProvider.ts
  56. +0 −1 src/webview/leetCodeSubmissionProvider.ts
  57. +29 −0 thirdpartynotice.txt
  58. +18 −26 tslint.json
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/question.md
Original file line number Diff line number Diff line change
@@ -3,11 +3,11 @@ name: 💬 Questions / Help
about: If you have questions, please check our documents first
---

Before you submit an question we recommend you to check out the [DOCUMENT](https://github.com/jdneo/vscode-leetcode/blob/master/README.md) first.
Before you submit an question we recommend you to check out the [DOCUMENT](https://github.com/LeetCode-OpenSource/vscode-leetcode/blob/master/README.md) first.

You can also find more information in:
- [TROUBLESHOOTING](https://github.com/jdneo/vscode-leetcode/wiki/Troubleshooting)
- [FAQ](https://github.com/jdneo/vscode-leetcode/wiki/FAQ)
- [TROUBLESHOOTING](https://github.com/LeetCode-OpenSource/vscode-leetcode/wiki/Troubleshooting)
- [FAQ](https://github.com/LeetCode-OpenSource/vscode-leetcode/wiki/FAQ)

## 💬 Questions and Help

50 changes: 50 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
linux:
name: Linux
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v2

- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: 14

- name: Install Node.js modules
run: npm install

- name: Lint
run: npm run lint

- name: VSCE Packge
run: npx vsce package

windows:
name: Windows
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v2

- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: 14

- name: Install Node.js modules
run: npm install

- name: Lint
run: npm run lint

- name: VSCE Packge
run: npx vsce package
19 changes: 0 additions & 19 deletions .travis.yml

This file was deleted.

9 changes: 6 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"editor.formatOnSave": true,
"editor.formatOnSave": false,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"files.insertFinalNewline": true,
@@ -10,5 +10,8 @@
".vscode-test": true
},
"tslint.autoFixOnSave": true,
"tslint.ignoreDefinitionFiles": true
}
"tslint.ignoreDefinitionFiles": true,
"prettier.tabWidth": 4,
"prettier.useTabs": false,
"prettier.printWidth": 150
}
1 change: 0 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ test/**
src/**
**/*.map
.gitignore
.travis.yml
package-lock.json
tsconfig.json
tslint.json
38 changes: 19 additions & 19 deletions ACKNOWLEDGEMENTS.md
Original file line number Diff line number Diff line change
@@ -3,22 +3,22 @@
A big thanks to the following individuals for contributing:

- [@JIEJIAN21](https://github.com/JIEJIAN21) - thanks for logo and icon design
- [@TsFreddie](https://github.com/TsFreddie)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=TsFreddie)
- [@ntt2k](https://github.com/ntt2k)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=ntt2k)
- [@purocean](https://github.com/purocean)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=purocean)
- [@Xeonacid](https://github.com/Xeonacid)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=Xeonacid)
- [@Himself65](https://github.com/Himself65)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=Himself65)
- [@Vigilans](https://github.com/Vigilans)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=Vigilans)
- [@ringcrl](https://github.com/ringcrl)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=ringcrl)
- [@pujiaxun](https://github.com/pujiaxun)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=pujiaxun)
- [@edvardchen](https://github.com/edvardchen)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=edvardchen)
- [@poppinlp](https://github.com/poppinlp)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=poppinlp)
- [@xuzaixian](https://github.com/xuzaixian)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=xuzaixian)
- [@ZainChen](https://github.com/ZainChen)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=ZainChen)
- [@houtianze](https://github.com/houtianze)[contributions](https://github.com/jdneo/vscode-leetcode/commits?author=houtianze)
- [@magic-akari](https://github.com/magic-akari) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=magic-akari)
- [@SF-Zhou](https://github.com/SF-Zhou) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=SF-Zhou)
- [@fuafa](https://github.com/fuafa) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=fuafa)
- [@iFun](https://github.com/iFun) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=iFun)
- [@hologerry](https://github.com/hologerry) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=hologerry)
- [@yihong0618](https://github.com/yihong0618) - [contributions](https://github.com/jdneo/vscode-leetcode/commits?author=yihong0618)
- [@TsFreddie](https://github.com/TsFreddie)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=TsFreddie)
- [@ntt2k](https://github.com/ntt2k)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=ntt2k)
- [@purocean](https://github.com/purocean)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=purocean)
- [@Xeonacid](https://github.com/Xeonacid)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=Xeonacid)
- [@Himself65](https://github.com/Himself65)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=Himself65)
- [@Vigilans](https://github.com/Vigilans)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=Vigilans)
- [@ringcrl](https://github.com/ringcrl)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=ringcrl)
- [@pujiaxun](https://github.com/pujiaxun)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=pujiaxun)
- [@edvardchen](https://github.com/edvardchen)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=edvardchen)
- [@poppinlp](https://github.com/poppinlp)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=poppinlp)
- [@xuzaixian](https://github.com/xuzaixian)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=xuzaixian)
- [@ZainChen](https://github.com/ZainChen)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=ZainChen)
- [@houtianze](https://github.com/houtianze)[contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=houtianze)
- [@magic-akari](https://github.com/magic-akari) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=magic-akari)
- [@SF-Zhou](https://github.com/SF-Zhou) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=SF-Zhou)
- [@fuafa](https://github.com/fuafa) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=fuafa)
- [@iFun](https://github.com/iFun) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=iFun)
- [@hologerry](https://github.com/hologerry) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=hologerry)
- [@yihong0618](https://github.com/yihong0618) - [contributions](https://github.com/LeetCode-OpenSource/vscode-leetcode/commits?author=yihong0618)
202 changes: 128 additions & 74 deletions CHANGELOG.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2018 jdneo
Copyright (c) 2020-present 力扣
Copyright (c) 2018-2019 jdneo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
113 changes: 61 additions & 52 deletions README.md

Large diffs are not rendered by default.

112 changes: 60 additions & 52 deletions docs/README_zh-CN.md

Large diffs are not rendered by default.

3,344 changes: 2,591 additions & 753 deletions package-lock.json

Large diffs are not rendered by default.

176 changes: 112 additions & 64 deletions package.json
Original file line number Diff line number Diff line change
@@ -2,19 +2,19 @@
"name": "vscode-leetcode",
"displayName": "LeetCode",
"description": "Solve LeetCode problems in VS Code",
"version": "0.15.8",
"author": "Sheng Chen",
"publisher": "shengchen",
"version": "0.18.4",
"author": "LeetCode",
"publisher": "LeetCode",
"license": "MIT",
"icon": "resources/LeetCode.png",
"engines": {
"vscode": "^1.30.1"
"vscode": "^1.57.0"
},
"repository": {
"type": "git",
"url": "https://github.com/jdneo/vscode-leetcode"
"url": "https://github.com/LeetCode-OpenSource/vscode-leetcode"
},
"homepage": "https://github.com/jdneo/vscode-leetcode/blob/master/README.md",
"homepage": "https://github.com/LeetCode-OpenSource/vscode-leetcode/blob/master/README.md",
"categories": [
"Other",
"Snippets"
@@ -38,17 +38,12 @@
"onCommand:leetcode.testSolution",
"onCommand:leetcode.submitSolution",
"onCommand:leetcode.switchDefaultLanguage",
"onCommand:leetcode.signinByCookie",
"onCommand:leetcode.problems.sort",
"onView:leetCodeExplorer"
],
"main": "./out/src/extension",
"contributes": {
"commands": [
{
"command": "leetcode.signinByCookie",
"title": "Sign In by Cookie",
"category": "LeetCode"
},
{
"command": "leetcode.deleteCache",
"title": "Delete Cache",
@@ -58,19 +53,13 @@
"command": "leetcode.toggleLeetCodeCn",
"title": "Switch Endpoint",
"category": "LeetCode",
"icon": {
"light": "resources/light/endpoint.svg",
"dark": "resources/dark/endpoint.svg"
}
"icon": "$(globe)"
},
{
"command": "leetcode.signin",
"title": "Sign In",
"category": "LeetCode",
"icon": {
"light": "resources/light/signin.svg",
"dark": "resources/dark/signin.svg"
}
"icon": "$(sign-in)"
},
{
"command": "leetcode.signout",
@@ -86,10 +75,7 @@
"command": "leetcode.refreshExplorer",
"title": "Refresh",
"category": "LeetCode",
"icon": {
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
"icon": "$(refresh)"
},
{
"command": "leetcode.pickOne",
@@ -110,10 +96,7 @@
"command": "leetcode.searchProblem",
"title": "Search Problem",
"category": "LeetCode",
"icon": {
"light": "resources/light/search.svg",
"dark": "resources/dark/search.svg"
}
"icon": "$(search)"
},
{
"command": "leetcode.showSolution",
@@ -152,6 +135,12 @@
"command": "leetcode.switchDefaultLanguage",
"title": "Switch Default Language",
"category": "LeetCode"
},
{
"command": "leetcode.problems.sort",
"title": "Sort Problems",
"category": "LeetCode",
"icon": "$(sort-precedence)"
}
],
"viewsContainers": {
@@ -193,10 +182,20 @@
"when": "view == leetCodeExplorer",
"group": "navigation@3"
},
{
"command": "leetcode.signout",
"when": "view == leetCodeExplorer",
"group": "overflow@1"
},
{
"command": "leetcode.pickOne",
"when": "view == leetCodeExplorer",
"group": "overflow@0"
"group": "overflow@2"
},
{
"command": "leetcode.problems.sort",
"when": "view == leetCodeExplorer",
"group": "overflow@3"
}
],
"view/item/context": [
@@ -261,6 +260,11 @@
}
],
"editor/context": [
{
"submenu": "leetcode.editorAction"
}
],
"leetcode.editorAction": [
{
"command": "leetcode.testSolution",
"group": "leetcode@1"
@@ -279,6 +283,12 @@
}
]
},
"submenus": [
{
"id": "leetcode.editorAction",
"label": "LeetCode"
}
],
"configuration": [
{
"title": "LeetCode",
@@ -289,12 +299,6 @@
"scope": "application",
"description": "Hide solved problems."
},
"leetcode.showLocked": {
"type": "boolean",
"default": false,
"scope": "application",
"description": "Show locked problems."
},
"leetcode.defaultLanguage": {
"type": "string",
"enum": [
@@ -313,16 +317,34 @@
"ruby",
"rust",
"scala",
"swift"
"swift",
"typescript"
],
"scope": "application",
"description": "Default language for solving the problems."
},
"leetcode.showDescription": {
"type": "string",
"default": "In Webview",
"enum": [
"In Webview",
"In File Comment",
"Both",
"None"
],
"enumDescriptions": [
"Show the problem description in a new webview window",
"Show the problem description in the file's comment"
],
"scope": "application",
"description": "Specify where to show the description."
},
"leetcode.showCommentDescription": {
"type": "boolean",
"default": false,
"scope": "application",
"description": "Include problem description in comments."
"description": "[Deprecated] Include problem description in comments.",
"deprecationMessage": "This setting will be deprecated in 0.17.0, please use 'leetcode.showDescription' instead"
},
"leetcode.hint.setDefaultLanguage": {
"type": "boolean",
@@ -364,17 +386,18 @@
],
"description": "Endpoint of the user account."
},
"leetcode.useEndpointTranslation": {
"type": "boolean",
"default": true,
"scope": "application",
"description": "Use endpoint's translation (if available)"
},
"leetcode.workspaceFolder": {
"type": "string",
"scope": "application",
"description": "The path of the workspace folder to store the problem files.",
"default": ""
},
"leetcode.outputFolder": {
"type": "string",
"scope": "application",
"description": "[Deprecated] The output folder to save the problem files."
},
"leetcode.filePath": {
"type": "object",
"scope": "application",
@@ -622,12 +645,6 @@
"scope": "application",
"description": "Show the LeetCode status bar or not."
},
"leetcode.enableShortcuts": {
"type": "boolean",
"default": true,
"scope": "application",
"description": "[Deprecated] Show the submit and test shortcuts in editor or not."
},
"leetcode.editor.shortcuts": {
"type": "array",
"default": [
@@ -640,11 +657,19 @@
"enum": [
"submit",
"test",
"star",
"solution",
"description"
],
"enumDescriptions": [
"Submit your answer to LeetCode.",
"Test your answer with customized test cases.",
"Star or unstar the current problem.",
"Show the top voted solution for the current problem.",
"Show the problem description page."
]
},
"description": "Customize the shorcuts in editors."
"description": "Customize the shortcuts in editors."
},
"leetcode.enableSideMode": {
"type": "boolean",
@@ -657,6 +682,29 @@
"default": "node",
"scope": "application",
"description": "The Node.js executable path. for example, C:\\Program Files\\nodejs\\node.exe"
},
"leetcode.colorizeProblems": {
"type": "boolean",
"default": true,
"scope": "application",
"description": "Add difficulty badge and colorize problems files in explorer tree."
},
"leetcode.problems.sortStrategy": {
"type": "string",
"default": "None",
"scope": "application",
"enum": [
"None",
"Acceptance Rate (Ascending)",
"Acceptance Rate (Descending)"
],
"description": "Sorting strategy for problems list."
},
"leetcode.allowReportData": {
"type": "boolean",
"default": true,
"scope": "application",
"description": "Allow LeetCode to report anonymous usage data to improve the product."
}
}
}
@@ -666,29 +714,29 @@
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test",
"lint": "tslint --project tsconfig.json -e src/*.d.ts -t verbose"
"lint": "tslint --project tsconfig.json -e src/*.d.ts -t verbose",
"build": "vsce package",
"vs-publish": "vsce publish"
},
"devDependencies": {
"@types/fs-extra": "5.0.0",
"@types/highlight.js": "^9.12.3",
"@types/lodash": "^4.14.123",
"@types/fs-extra": "^9.0.11",
"@types/lodash": "^4.14.170",
"@types/markdown-it": "0.0.7",
"@types/mocha": "^2.2.42",
"@types/node": "^7.0.43",
"@types/node": "^14.14.33",
"@types/require-from-string": "^1.2.0",
"tslint": "^5.9.1",
"typescript": "^2.6.1",
"vscode": "^1.1.33"
"@types/vscode": "1.57.0",
"tslint": "^5.20.1",
"typescript": "^4.3.2"
},
"dependencies": {
"fs-extra": "^6.0.1",
"highlight.js": "^9.15.6",
"lodash": "^4.17.13",
"axios": "^1.6.8",
"fs-extra": "^10.0.0",
"highlight.js": "^10.7.2",
"lodash": "^4.17.21",
"markdown-it": "^8.4.2",
"require-from-string": "^2.0.2",
"unescape-js": "^1.1.1",
"vsc-leetcode-cli": "2.6.19"
"unescape-js": "^1.1.4",
"vsc-leetcode-cli": "2.8.1"
}
}
Binary file modified resources/LeetCode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 5 additions & 7 deletions resources/LeetCode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/check.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/dark/dislike.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions resources/dark/endpoint.svg

This file was deleted.

Binary file modified resources/dark/like.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions resources/dark/refresh.svg

This file was deleted.

3 changes: 0 additions & 3 deletions resources/dark/search.svg

This file was deleted.

3 changes: 0 additions & 3 deletions resources/dark/signin.svg

This file was deleted.

Binary file modified resources/light/dislike.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions resources/light/endpoint.svg

This file was deleted.

Binary file modified resources/light/like.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions resources/light/refresh.svg

This file was deleted.

3 changes: 0 additions & 3 deletions resources/light/search.svg

This file was deleted.

3 changes: 0 additions & 3 deletions resources/light/signin.svg

This file was deleted.

Binary file modified resources/lock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified resources/x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 5 additions & 21 deletions src/codelens/CodeLensController.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import { ConfigurationChangeEvent, Disposable, languages, workspace, WorkspaceConfiguration } from "vscode";
import { CustomCodeLensProvider } from "./CustomCodeLensProvider";
import { ConfigurationChangeEvent, Disposable, languages, workspace } from "vscode";
import { customCodeLensProvider, CustomCodeLensProvider } from "./CustomCodeLensProvider";

class CodeLensController implements Disposable {
private internalProvider: CustomCodeLensProvider;
private registeredProvider: Disposable | undefined;
private configurationChangeListener: Disposable;

constructor() {
this.internalProvider = new CustomCodeLensProvider();
this.internalProvider = customCodeLensProvider;

this.configurationChangeListener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration("leetcode.enableShortcuts")) {
this.setCodeLensVisibility();
} else if (event.affectsConfiguration("leetcode.editor.shortcuts")) {
if (event.affectsConfiguration("leetcode.editor.shortcuts")) {
this.internalProvider.refresh();
}
}, this);

this.setCodeLensVisibility();
this.registeredProvider = languages.registerCodeLensProvider({ scheme: "file" }, this.internalProvider);
}

public dispose(): void {
@@ -29,20 +27,6 @@ class CodeLensController implements Disposable {
}
this.configurationChangeListener.dispose();
}

private setCodeLensVisibility(): void {
if (this.isShortcutsEnabled() && !this.registeredProvider) {
this.registeredProvider = languages.registerCodeLensProvider({ scheme: "file" }, this.internalProvider);
} else if (!this.isShortcutsEnabled() && this.registeredProvider) {
this.registeredProvider.dispose();
this.registeredProvider = undefined;
}
}

private isShortcutsEnabled(): boolean {
const configuration: WorkspaceConfiguration = workspace.getConfiguration();
return configuration.get<boolean>("leetcode.enableShortcuts", true);
}
}

export const codeLensController: CodeLensController = new CodeLensController();
19 changes: 18 additions & 1 deletion src/codelens/CustomCodeLensProvider.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
// Licensed under the MIT license.

import * as vscode from "vscode";
import { explorerNodeManager } from "../explorer/explorerNodeManager";
import { LeetCodeNode } from "../explorer/LeetCodeNode";
import { getEditorShortcuts } from "../utils/settingUtils";

export class CustomCodeLensProvider implements vscode.CodeLensProvider {
@@ -23,10 +25,15 @@ export class CustomCodeLensProvider implements vscode.CodeLensProvider {
}

const content: string = document.getText();
const matchResult: RegExpMatchArray | null = content.match(/@lc app=.* id=.* lang=.*/);
const matchResult: RegExpMatchArray | null = content.match(/@lc app=.* id=(.*) lang=.*/);
if (!matchResult) {
return undefined;
}
const nodeId: string | undefined = matchResult[1];
let node: LeetCodeNode | undefined;
if (nodeId) {
node = explorerNodeManager.getNodeById(nodeId);
}

let codeLensLine: number = document.lineCount - 1;
for (let i: number = document.lineCount - 1; i >= 0; i--) {
@@ -56,6 +63,14 @@ export class CustomCodeLensProvider implements vscode.CodeLensProvider {
}));
}

if (shortcuts.indexOf("star") >= 0 && node) {
codeLens.push(new vscode.CodeLens(range, {
title: node.isFavorite ? "Unstar" : "Star",
command: node.isFavorite ? "leetcode.removeFavorite" : "leetcode.addFavorite",
arguments: [node],
}));
}

if (shortcuts.indexOf("solution") >= 0) {
codeLens.push(new vscode.CodeLens(range, {
title: "Solution",
@@ -75,3 +90,5 @@ export class CustomCodeLensProvider implements vscode.CodeLensProvider {
return codeLens;
}
}

export const customCodeLensProvider: CustomCodeLensProvider = new CustomCodeLensProvider();
8 changes: 4 additions & 4 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";
import { leetCodeExecutor } from "../leetCodeExecutor";
import { leetCodeManager } from "../leetCodeManager";
import { IProblem, ProblemState, UserStatus } from "../shared";
import * as settingUtils from "../utils/settingUtils";
import { DialogType, promptForOpenOutputChannel } from "../utils/uiUtils";

export async function listProblems(): Promise<IProblem[]> {
try {
if (leetCodeManager.getStatus() === UserStatus.SignedOut) {
return [];
}
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
const showLocked: boolean = !!leetCodeConfig.get<boolean>("showLocked");
const result: string = await leetCodeExecutor.listProblems(showLocked);

const useEndpointTranslation: boolean = settingUtils.shouldUseEndpointTranslation();
const result: string = await leetCodeExecutor.listProblems(true, useEndpointTranslation);
const problems: IProblem[] = [];
const lines: string[] = result.split("\n");
const reg: RegExp = /^(.)\s(.{1,2})\s(.)\s\[\s*(\d*)\s*\]\s*(.*)\s*(Easy|Medium|Hard)\s*\((\s*\d+\.\d+ %)\)/;
38 changes: 36 additions & 2 deletions src/commands/plugin.ts
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@
// Licensed under the MIT license.

import * as vscode from "vscode";
import { leetCodeTreeDataProvider } from "../explorer/LeetCodeTreeDataProvider";
import { leetCodeExecutor } from "../leetCodeExecutor";
import { IQuickItemEx } from "../shared";
import { Endpoint } from "../shared";
import { Endpoint, SortingStrategy } from "../shared";
import { DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils";
import { deleteCache } from "./cache";

@@ -20,7 +21,7 @@ export async function switchEndpoint(): Promise<void> {
},
{
label: `${isCnEnabled ? "$(check) " : ""}力扣`,
description: "leetcode-cn.com",
description: "leetcode.cn",
detail: `启用中国版 LeetCode`,
value: Endpoint.LeetCodeCN,
},
@@ -52,3 +53,36 @@ export function getLeetCodeEndpoint(): string {
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
return leetCodeConfig.get<string>("endpoint", Endpoint.LeetCode);
}

const SORT_ORDER: SortingStrategy[] = [
SortingStrategy.None,
SortingStrategy.AcceptanceRateAsc,
SortingStrategy.AcceptanceRateDesc,
];

export async function switchSortingStrategy(): Promise<void> {
const currentStrategy: SortingStrategy = getSortingStrategy();
const picks: Array<IQuickItemEx<string>> = [];
picks.push(
...SORT_ORDER.map((s: SortingStrategy) => {
return {
label: `${currentStrategy === s ? "$(check)" : " "} ${s}`,
value: s,
};
}),
);

const choice: IQuickItemEx<string> | undefined = await vscode.window.showQuickPick(picks);
if (!choice || choice.value === currentStrategy) {
return;
}

const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
await leetCodeConfig.update("problems.sortStrategy", choice.value, true);
await leetCodeTreeDataProvider.refresh();
}

export function getSortingStrategy(): SortingStrategy {
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
return leetCodeConfig.get<SortingStrategy>("problems.sortStrategy", SortingStrategy.None);
}
128 changes: 79 additions & 49 deletions src/commands/show.ts
Original file line number Diff line number Diff line change
@@ -10,17 +10,30 @@ import { LeetCodeNode } from "../explorer/LeetCodeNode";
import { leetCodeChannel } from "../leetCodeChannel";
import { leetCodeExecutor } from "../leetCodeExecutor";
import { leetCodeManager } from "../leetCodeManager";
import { IProblem, IQuickItemEx, languages, ProblemState } from "../shared";
import { Endpoint, IProblem, IQuickItemEx, languages, PREMIUM_URL_CN, PREMIUM_URL_GLOBAL, ProblemState } from "../shared";
import { genFileExt, genFileName, getNodeIdFromFile } from "../utils/problemUtils";
import { DialogOptions, DialogType, openSettingsEditor, promptForOpenOutputChannel, promptForSignIn, promptHintMessage } from "../utils/uiUtils";
import * as settingUtils from "../utils/settingUtils";
import { IDescriptionConfiguration } from "../utils/settingUtils";
import {
DialogOptions,
DialogType,
openSettingsEditor,
openUrl,
promptForOpenOutputChannel,
promptForSignIn,
promptHintMessage,
} from "../utils/uiUtils";
import { getActiveFilePath, selectWorkspaceFolder } from "../utils/workspaceUtils";
import * as wsl from "../utils/wslUtils";
import { leetCodePreviewProvider } from "../webview/leetCodePreviewProvider";
import { leetCodeSolutionProvider } from "../webview/leetCodeSolutionProvider";
import * as list from "./list";
import { getLeetCodeEndpoint } from "./plugin";
import { globalState } from "../globalState";

export async function previewProblem(input: IProblem | vscode.Uri, isSideMode: boolean = false): Promise<void> {
let node: IProblem;

if (input instanceof vscode.Uri) {
const activeFilePath: string = input.fsPath;
const id: string = await getNodeIdFromFile(activeFilePath);
@@ -38,9 +51,16 @@ export async function previewProblem(input: IProblem | vscode.Uri, isSideMode: b
isSideMode = true;
} else {
node = input;
const { isPremium } = globalState.getUserStatus() ?? {};
if (input.locked && !isPremium) {
const url = getLeetCodeEndpoint() === Endpoint.LeetCode ? PREMIUM_URL_GLOBAL : PREMIUM_URL_CN;
openUrl(url);
return;
}
}

const descString: string = await leetCodeExecutor.getDescription(node.id);
const needTranslation: boolean = settingUtils.shouldUseEndpointTranslation();
const descString: string = await leetCodeExecutor.getDescription(node.id, needTranslation);
leetCodePreviewProvider.show(descString, node, isSideMode);
}

@@ -62,13 +82,10 @@ export async function searchProblem(): Promise<void> {
promptForSignIn();
return;
}
const choice: IQuickItemEx<IProblem> | undefined = await vscode.window.showQuickPick(
parseProblemsToPicks(list.listProblems()),
{
matchOnDetail: true,
placeHolder: "Select one problem",
},
);
const choice: IQuickItemEx<IProblem> | undefined = await vscode.window.showQuickPick(parseProblemsToPicks(list.listProblems()), {
matchOnDetail: true,
placeHolder: "Select one problem",
});
if (!choice) {
return;
}
@@ -77,11 +94,14 @@ export async function searchProblem(): Promise<void> {

export async function showSolution(input: LeetCodeNode | vscode.Uri): Promise<void> {
let problemInput: string | undefined;
if (input instanceof LeetCodeNode) { // Triggerred from explorer
if (input instanceof LeetCodeNode) {
// Triggerred from explorer
problemInput = input.id;
} else if (input instanceof vscode.Uri) { // Triggerred from Code Lens/context menu
} else if (input instanceof vscode.Uri) {
// Triggerred from Code Lens/context menu
problemInput = `"${input.fsPath}"`;
} else if (!input) { // Triggerred from command
} else if (!input) {
// Triggerred from command
problemInput = await getActiveFilePath();
}

@@ -95,7 +115,8 @@ export async function showSolution(input: LeetCodeNode | vscode.Uri): Promise<vo
return;
}
try {
const solution: string = await leetCodeExecutor.showSolution(problemInput, language);
const needTranslation: boolean = settingUtils.shouldUseEndpointTranslation();
const solution: string = await leetCodeExecutor.showSolution(problemInput, language, needTranslation);
leetCodeSolutionProvider.show(unescapeJS(solution));
} catch (error) {
leetCodeChannel.appendLine(error.toString());
@@ -109,15 +130,20 @@ async function fetchProblemLanguage(): Promise<string | undefined> {
if (defaultLanguage && languages.indexOf(defaultLanguage) < 0) {
defaultLanguage = undefined;
}
const language: string | undefined = defaultLanguage || await vscode.window.showQuickPick(languages, { placeHolder: "Select the language you want to use", ignoreFocusOut: true });
const language: string | undefined =
defaultLanguage ||
(await vscode.window.showQuickPick(languages, {
placeHolder: "Select the language you want to use",
ignoreFocusOut: true,
}));
// fire-and-forget default language query
(async (): Promise<void> => {
if (language && !defaultLanguage && leetCodeConfig.get<boolean>("hint.setDefaultLanguage")) {
const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage(
`Would you like to set '${language}' as your default language?`,
DialogOptions.yes,
DialogOptions.no,
DialogOptions.never,
DialogOptions.never
);
if (choice === DialogOptions.yes) {
leetCodeConfig.update("defaultLanguage", language, true /* UserSetting */);
@@ -142,16 +168,11 @@ async function showProblemInternal(node: IProblem): Promise<void> {
return;
}

const outputFolder: string = leetCodeConfig.get<string>("outputFolder", "").trim();

const fileFolder: string = leetCodeConfig
.get<string>(`filePath.${language}.folder`, leetCodeConfig.get<string>(`filePath.default.folder`, outputFolder))
.get<string>(`filePath.${language}.folder`, leetCodeConfig.get<string>(`filePath.default.folder`, ""))
.trim();
const fileName: string = leetCodeConfig
.get<string>(
`filePath.${language}.filename`,
leetCodeConfig.get<string>(`filePath.default.filename`) || genFileName(node, language),
)
.get<string>(`filePath.${language}.filename`, leetCodeConfig.get<string>(`filePath.default.filename`) || genFileName(node, language))
.trim();

let finalPath: string = path.join(workspaceFolder, fileFolder, fileName);
@@ -166,36 +187,48 @@ async function showProblemInternal(node: IProblem): Promise<void> {

finalPath = wsl.useWsl() ? await wsl.toWinPath(finalPath) : finalPath;

await leetCodeExecutor.showProblem(node, language, finalPath, leetCodeConfig.get<boolean>("showCommentDescription"));
await Promise.all([
vscode.window.showTextDocument(vscode.Uri.file(finalPath), { preview: false, viewColumn: vscode.ViewColumn.One }),
movePreviewAsideIfNeeded(node),
const descriptionConfig: IDescriptionConfiguration = settingUtils.getDescriptionConfiguration();
const needTranslation: boolean = settingUtils.shouldUseEndpointTranslation();

await leetCodeExecutor.showProblem(node, language, finalPath, descriptionConfig.showInComment, needTranslation);
const promises: any[] = [
vscode.window.showTextDocument(vscode.Uri.file(finalPath), {
preview: false,
viewColumn: vscode.ViewColumn.One,
}),
promptHintMessage(
"hint.commentDescription",
'You can generate the code file with problem description in the comments by enabling "leetcode.showCommentDescription".',
'You can config how to show the problem description through "leetcode.showDescription".',
"Open settings",
(): Promise<any> => openSettingsEditor("leetcode.showCommentDescription"),
(): Promise<any> => openSettingsEditor("leetcode.showDescription")
),
]);
];
if (descriptionConfig.showInWebview) {
promises.push(showDescriptionView(node));
}

await Promise.all(promises);
} catch (error) {
await promptForOpenOutputChannel(`${error} Please open the output channel for details.`, DialogType.error);
}
}

async function movePreviewAsideIfNeeded(node: IProblem): Promise<void> {
if (vscode.workspace.getConfiguration("leetcode").get<boolean>("enableSideMode", true)) {
return previewProblem(node, true);
}
async function showDescriptionView(node: IProblem): Promise<void> {
return previewProblem(node, vscode.workspace.getConfiguration("leetcode").get<boolean>("enableSideMode", true));
}

async function parseProblemsToPicks(p: Promise<IProblem[]>): Promise<Array<IQuickItemEx<IProblem>>> {
return new Promise(async (resolve: (res: Array<IQuickItemEx<IProblem>>) => void): Promise<void> => {
const picks: Array<IQuickItemEx<IProblem>> = (await p).map((problem: IProblem) => Object.assign({}, {
label: `${parseProblemDecorator(problem.state, problem.locked)}${problem.id}.${problem.name}`,
description: "",
detail: `AC rate: ${problem.passRate}, Difficulty: ${problem.difficulty}`,
value: problem,
}));
const picks: Array<IQuickItemEx<IProblem>> = (await p).map((problem: IProblem) =>
Object.assign(
{},
{
label: `${parseProblemDecorator(problem.state, problem.locked)}${problem.id}.${problem.name}`,
description: "",
detail: `AC rate: ${problem.passRate}, Difficulty: ${problem.difficulty}`,
value: problem,
}
)
);
resolve(picks);
});
}
@@ -261,14 +294,11 @@ async function resolveTagForProblem(problem: IProblem): Promise<string | undefin
if (problem.tags.length === 1) {
return problem.tags[0];
}
return await vscode.window.showQuickPick(
problem.tags,
{
matchOnDetail: true,
placeHolder: "Multiple tags available, please select one",
ignoreFocusOut: true,
},
);
return await vscode.window.showQuickPick(problem.tags, {
matchOnDetail: true,
placeHolder: "Multiple tags available, please select one",
ignoreFocusOut: true,
});
}

async function resolveCompanyForProblem(problem: IProblem): Promise<string | undefined> {
12 changes: 10 additions & 2 deletions src/commands/star.ts
Original file line number Diff line number Diff line change
@@ -2,15 +2,20 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import { customCodeLensProvider } from "../codelens/CustomCodeLensProvider";
import { LeetCodeNode } from "../explorer/LeetCodeNode";
import { leetCodeTreeDataProvider } from "../explorer/LeetCodeTreeDataProvider";
import { leetCodeExecutor } from "../leetCodeExecutor";
import { hasStarShortcut } from "../utils/settingUtils";
import { DialogType, promptForOpenOutputChannel } from "../utils/uiUtils";

export async function addFavorite(node: LeetCodeNode): Promise<void> {
try {
await leetCodeExecutor.toggleFavorite(node, true);
leetCodeTreeDataProvider.refresh();
await leetCodeTreeDataProvider.refresh();
if (hasStarShortcut()) {
customCodeLensProvider.refresh();
}
} catch (error) {
await promptForOpenOutputChannel("Failed to add the problem to favorite. Please open the output channel for details.", DialogType.error);
}
@@ -19,7 +24,10 @@ export async function addFavorite(node: LeetCodeNode): Promise<void> {
export async function removeFavorite(node: LeetCodeNode): Promise<void> {
try {
await leetCodeExecutor.toggleFavorite(node, false);
leetCodeTreeDataProvider.refresh();
await leetCodeTreeDataProvider.refresh();
if (hasStarShortcut()) {
customCodeLensProvider.refresh();
}
} catch (error) {
await promptForOpenOutputChannel("Failed to remove the problem from favorite. Please open the output channel for details.", DialogType.error);
}
15 changes: 14 additions & 1 deletion src/explorer/LeetCodeNode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import { Command } from "vscode";
import { Command, Uri } from "vscode";
import { IProblem, ProblemState } from "../shared";

export class LeetCodeNode {
@@ -55,4 +55,17 @@ export class LeetCodeNode {
};
}

public get acceptanceRate(): number {
return Number(this.passRate.slice(0, -1).trim());
}

public get uri(): Uri {
return Uri.from({
scheme: "leetcode",
authority: this.isProblem ? "problems" : "tree-node",
path: `/${this.id}`, // path must begin with slash /
query: `difficulty=${this.difficulty}`,
});
}

}
44 changes: 28 additions & 16 deletions src/explorer/LeetCodeTreeDataProvider.ts
Original file line number Diff line number Diff line change
@@ -8,12 +8,14 @@ import { leetCodeManager } from "../leetCodeManager";
import { Category, defaultProblem, ProblemState } from "../shared";
import { explorerNodeManager } from "./explorerNodeManager";
import { LeetCodeNode } from "./LeetCodeNode";
import { globalState } from "../globalState";

export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCodeNode> {

private context: vscode.ExtensionContext;

private onDidChangeTreeDataEvent: vscode.EventEmitter<LeetCodeNode | undefined | null> = new vscode.EventEmitter<LeetCodeNode | undefined | null>();
private onDidChangeTreeDataEvent: vscode.EventEmitter<LeetCodeNode | undefined | null> = new vscode.EventEmitter<
LeetCodeNode | undefined | null
>();
// tslint:disable-next-line:member-ordering
public readonly onDidChangeTreeData: vscode.Event<any> = this.onDidChangeTreeDataEvent.event;

@@ -23,7 +25,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod

public async refresh(): Promise<void> {
await explorerNodeManager.refreshCache();
this.onDidChangeTreeDataEvent.fire();
this.onDidChangeTreeDataEvent.fire(null);
}

public getTreeItem(element: LeetCodeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
@@ -46,28 +48,33 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod
}

return {
label: element.isProblem ? `[${element.id}] ${element.name}` : element.name,
label: element.isProblem ? `[${element.id}] ${element.name}` + this.parsePremiumUnLockIconPath(element) : element.name,
tooltip: this.getSubCategoryTooltip(element),
collapsibleState: element.isProblem ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
iconPath: this.parseIconPathFromProblemState(element),
command: element.isProblem ? element.previewCommand : undefined,
resourceUri: element.uri,
contextValue,
};
}

public getChildren(element?: LeetCodeNode | undefined): vscode.ProviderResult<LeetCodeNode[]> {
if (!leetCodeManager.getUser()) {
return [
new LeetCodeNode(Object.assign({}, defaultProblem, {
id: "notSignIn",
name: "Sign in to LeetCode",
}), false),
new LeetCodeNode(
Object.assign({}, defaultProblem, {
id: "notSignIn",
name: "Sign in to LeetCode",
}),
false
),
];
}
if (!element) { // Root view
if (!element) {
// Root view
return explorerNodeManager.getRootNodes();
} else {
switch (element.id) { // First-level
switch (element.id) {
case Category.All:
return explorerNodeManager.getAllNodes();
case Category.Favorite:
@@ -91,13 +98,14 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod
if (!element.isProblem) {
return "";
}
const { isPremium } = globalState.getUserStatus() ?? {};
switch (element.state) {
case ProblemState.AC:
return this.context.asAbsolutePath(path.join("resources", "check.png"));
case ProblemState.NotAC:
return this.context.asAbsolutePath(path.join("resources", "x.png"));
case ProblemState.Unknown:
if (element.locked) {
if (element.locked && !isPremium) {
return this.context.asAbsolutePath(path.join("resources", "lock.png"));
}
return this.context.asAbsolutePath(path.join("resources", "blank.png"));
@@ -106,6 +114,14 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod
}
}

private parsePremiumUnLockIconPath(element: LeetCodeNode): string {
const { isPremium } = globalState.getUserStatus() ?? {};
if (isPremium && element.locked) {
return " 🔓";
}
return "";
}

private getSubCategoryTooltip(element: LeetCodeNode): string {
// return '' unless it is a sub-category node
if (element.isProblem || element.id === "ROOT" || element.id in Category) {
@@ -129,11 +145,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod
}
}

return [
`AC: ${acceptedNum}`,
`Failed: ${failedNum}`,
`Total: ${childernNodes.length}`,
].join(os.EOL);
return [`AC: ${acceptedNum}`, `Failed: ${failedNum}`, `Total: ${childernNodes.length}`].join(os.EOL);
}
}

40 changes: 40 additions & 0 deletions src/explorer/LeetCodeTreeItemDecorationProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { URLSearchParams } from "url";
import { FileDecoration, FileDecorationProvider, ProviderResult, ThemeColor, Uri, workspace, WorkspaceConfiguration } from "vscode";

export class LeetCodeTreeItemDecorationProvider implements FileDecorationProvider {
private readonly DIFFICULTY_BADGE_LABEL: { [key: string]: string } = {
easy: "E",
medium: "M",
hard: "H",
};

private readonly ITEM_COLOR: { [key: string]: ThemeColor } = {
easy: new ThemeColor("charts.green"),
medium: new ThemeColor("charts.yellow"),
hard: new ThemeColor("charts.red"),
};

public provideFileDecoration(uri: Uri): ProviderResult<FileDecoration> {
if (!this.isDifficultyBadgeEnabled()) {
return;
}

if (uri.scheme !== "leetcode" && uri.authority !== "problems") {
return;
}

const params: URLSearchParams = new URLSearchParams(uri.query);
const difficulty: string = params.get("difficulty")!.toLowerCase();
return {
badge: this.DIFFICULTY_BADGE_LABEL[difficulty],
color: this.ITEM_COLOR[difficulty],
};
}

private isDifficultyBadgeEnabled(): boolean {
const configuration: WorkspaceConfiguration = workspace.getConfiguration();
return configuration.get<boolean>("leetcode.colorizeProblems", false);
}
}

export const leetCodeTreeItemDecorationProvider: LeetCodeTreeItemDecorationProvider = new LeetCodeTreeItemDecorationProvider();
20 changes: 16 additions & 4 deletions src/explorer/explorerNodeManager.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,8 @@
import * as _ from "lodash";
import { Disposable } from "vscode";
import * as list from "../commands/list";
import { Category, defaultProblem, ProblemState } from "../shared";
import { getSortingStrategy } from "../commands/plugin";
import { Category, defaultProblem, ProblemState, SortingStrategy } from "../shared";
import { shouldHideSolvedProblem } from "../utils/settingUtils";
import { LeetCodeNode } from "./LeetCodeNode";

@@ -56,7 +57,9 @@ class ExplorerNodeManager implements Disposable {
}

public getAllNodes(): LeetCodeNode[] {
return Array.from(this.explorerNodeMap.values());
return this.applySortingStrategy(
Array.from(this.explorerNodeMap.values()),
);
}

public getAllDifficultyNodes(): LeetCodeNode[] {
@@ -114,7 +117,7 @@ class ExplorerNodeManager implements Disposable {
res.push(node);
}
}
return res;
return this.applySortingStrategy(res);
}

public getChildrenNodesById(id: string): LeetCodeNode[] {
@@ -142,7 +145,7 @@ class ExplorerNodeManager implements Disposable {
break;
}
}
return res;
return this.applySortingStrategy(res);
}

public dispose(): void {
@@ -186,6 +189,15 @@ class ExplorerNodeManager implements Disposable {
break;
}
}

private applySortingStrategy(nodes: LeetCodeNode[]): LeetCodeNode[] {
const strategy: SortingStrategy = getSortingStrategy();
switch (strategy) {
case SortingStrategy.AcceptanceRateAsc: return nodes.sort((x: LeetCodeNode, y: LeetCodeNode) => Number(x.acceptanceRate) - Number(y.acceptanceRate));
case SortingStrategy.AcceptanceRateDesc: return nodes.sort((x: LeetCodeNode, y: LeetCodeNode) => Number(y.acceptanceRate) - Number(x.acceptanceRate));
default: return nodes;
}
}
}

export const explorerNodeManager: ExplorerNodeManager = new ExplorerNodeManager();
44 changes: 39 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import * as test from "./commands/test";
import { explorerNodeManager } from "./explorer/explorerNodeManager";
import { LeetCodeNode } from "./explorer/LeetCodeNode";
import { leetCodeTreeDataProvider } from "./explorer/LeetCodeTreeDataProvider";
import { leetCodeTreeItemDecorationProvider } from "./explorer/LeetCodeTreeItemDecorationProvider";
import { leetCodeChannel } from "./leetCodeChannel";
import { leetCodeExecutor } from "./leetCodeExecutor";
import { leetCodeManager } from "./leetCodeManager";
@@ -23,10 +24,12 @@ import { leetCodePreviewProvider } from "./webview/leetCodePreviewProvider";
import { leetCodeSolutionProvider } from "./webview/leetCodeSolutionProvider";
import { leetCodeSubmissionProvider } from "./webview/leetCodeSubmissionProvider";
import { markdownEngine } from "./webview/markdownEngine";
import TrackData from "./utils/trackingUtils";
import { globalState } from "./globalState";

export async function activate(context: vscode.ExtensionContext): Promise<void> {
try {
if (!await leetCodeExecutor.meetRequirements()) {
if (!(await leetCodeExecutor.meetRequirements(context))) {
throw new Error("The environment doesn't meet requirements.");
}

@@ -36,6 +39,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
});

leetCodeTreeDataProvider.initialize(context);
globalState.initialize(context);

context.subscriptions.push(
leetCodeStatusBarController,
@@ -47,28 +51,58 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
markdownEngine,
codeLensController,
explorerNodeManager,
vscode.window.registerFileDecorationProvider(leetCodeTreeItemDecorationProvider),
vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }),
vscode.commands.registerCommand("leetcode.deleteCache", () => cache.deleteCache()),
vscode.commands.registerCommand("leetcode.toggleLeetCodeCn", () => plugin.switchEndpoint()),
vscode.commands.registerCommand("leetcode.signin", () => leetCodeManager.signIn()),
vscode.commands.registerCommand("leetcode.signinByCookie", () => leetCodeManager.signIn(true)),
vscode.commands.registerCommand("leetcode.signout", () => leetCodeManager.signOut()),
vscode.commands.registerCommand("leetcode.manageSessions", () => session.manageSessions()),
vscode.commands.registerCommand("leetcode.previewProblem", (node: LeetCodeNode) => show.previewProblem(node)),
vscode.commands.registerCommand("leetcode.previewProblem", (node: LeetCodeNode) => {
TrackData.report({
event_key: `vscode_open_problem`,
type: "click",
extra: JSON.stringify({
problem_id: node.id,
problem_name: node.name,
}),
});
show.previewProblem(node);
}),
vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(node)),
vscode.commands.registerCommand("leetcode.pickOne", () => show.pickOne()),
vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem()),
vscode.commands.registerCommand("leetcode.showSolution", (input: LeetCodeNode | vscode.Uri) => show.showSolution(input)),
vscode.commands.registerCommand("leetcode.refreshExplorer", () => leetCodeTreeDataProvider.refresh()),
vscode.commands.registerCommand("leetcode.testSolution", (uri?: vscode.Uri) => test.testSolution(uri)),
vscode.commands.registerCommand("leetcode.submitSolution", (uri?: vscode.Uri) => submit.submitSolution(uri)),
vscode.commands.registerCommand("leetcode.testSolution", (uri?: vscode.Uri) => {
TrackData.report({
event_key: `vscode_runCode`,
type: "click",
extra: JSON.stringify({
path: uri?.path,
}),
});
return test.testSolution(uri);
}),
vscode.commands.registerCommand("leetcode.submitSolution", (uri?: vscode.Uri) => {
TrackData.report({
event_key: `vscode_submit`,
type: "click",
extra: JSON.stringify({
path: uri?.path,
}),
});
return submit.submitSolution(uri);
}),
vscode.commands.registerCommand("leetcode.switchDefaultLanguage", () => switchDefaultLanguage()),
vscode.commands.registerCommand("leetcode.addFavorite", (node: LeetCodeNode) => star.addFavorite(node)),
vscode.commands.registerCommand("leetcode.removeFavorite", (node: LeetCodeNode) => star.removeFavorite(node)),
vscode.commands.registerCommand("leetcode.problems.sort", () => plugin.switchSortingStrategy())
);

await leetCodeExecutor.switchEndpoint(plugin.getLeetCodeEndpoint());
await leetCodeManager.getLoginStatus();
vscode.window.registerUriHandler({ handleUri: leetCodeManager.handleUriSignIn });
} catch (error) {
leetCodeChannel.appendLine(error.toString());
promptForOpenOutputChannel("Extension initialization failed. Please open output channel for details.", DialogType.error);
55 changes: 55 additions & 0 deletions src/globalState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) leo.zhao. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";

const CookieKey = "leetcode-cookie";
const UserStatusKey = "leetcode-user-status";

export type UserDataType = {
isSignedIn: boolean;
isPremium: boolean;
username: string;
avatar: string;
isVerified?: boolean;
};

class GlobalState {
private context: vscode.ExtensionContext;
private _state: vscode.Memento;
private _cookie: string;
private _userStatus: UserDataType;

public initialize(context: vscode.ExtensionContext): void {
this.context = context;
this._state = this.context.globalState;
}

public setCookie(cookie: string): any {
this._cookie = cookie;
return this._state.update(CookieKey, this._cookie);
}
public getCookie(): string | undefined {
return this._cookie ?? this._state.get(CookieKey);
}

public setUserStatus(userStatus: UserDataType): any {
this._userStatus = userStatus;
return this._state.update(UserStatusKey, this._userStatus);
}

public getUserStatus(): UserDataType | undefined {
return this._userStatus ?? this._state.get(UserStatusKey);
}

public removeCookie(): void {
this._state.update(CookieKey, undefined);
}

public removeAll(): void {
this._state.update(CookieKey, undefined);
this._state.update(UserStatusKey, undefined);
}
}

export const globalState: GlobalState = new GlobalState();
98 changes: 69 additions & 29 deletions src/leetCodeExecutor.ts
Original file line number Diff line number Diff line change
@@ -3,24 +3,24 @@

import * as cp from "child_process";
import * as fse from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as requireFromString from "require-from-string";
import { ExtensionContext } from "vscode";
import { ConfigurationChangeEvent, Disposable, MessageItem, window, workspace, WorkspaceConfiguration } from "vscode";
import { Endpoint, IProblem, supportedPlugins } from "./shared";
import { Endpoint, IProblem, leetcodeHasInited, supportedPlugins } from "./shared";
import { executeCommand, executeCommandWithProgress } from "./utils/cpUtils";
import { DialogOptions, openUrl } from "./utils/uiUtils";
import * as wsl from "./utils/wslUtils";
import { toWslPath, useWsl } from "./utils/wslUtils";

class LeetCodeExecutor implements Disposable {
private leetCodeRootPath: string;
private leetCodeRootPathInWsl: string;
private nodeExecutable: string;
private configurationChangeListener: Disposable;

constructor() {
this.leetCodeRootPath = path.join(__dirname, "..", "..", "node_modules", "vsc-leetcode-cli");
this.leetCodeRootPathInWsl = "";
this.nodeExecutable = this.getNodePath();
this.configurationChangeListener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration("leetcode.nodePath")) {
@@ -29,21 +29,18 @@ class LeetCodeExecutor implements Disposable {
}, this);
}

public async getLeetCodeRootPath(): Promise<string> { // not wrapped by ""
public async getLeetCodeBinaryPath(): Promise<string> {
if (wsl.useWsl()) {
if (!this.leetCodeRootPathInWsl) {
this.leetCodeRootPathInWsl = `${await wsl.toWslPath(this.leetCodeRootPath)}`;
}
return `${this.leetCodeRootPathInWsl}`;
return `${await wsl.toWslPath(`"${path.join(this.leetCodeRootPath, "bin", "leetcode")}"`)}`;
}
return `${this.leetCodeRootPath}`;
}

public async getLeetCodeBinaryPath(): Promise<string> { // wrapped by ""
return `"${path.join(await this.getLeetCodeRootPath(), "bin", "leetcode")}"`;
return `"${path.join(this.leetCodeRootPath, "bin", "leetcode")}"`;
}

public async meetRequirements(): Promise<boolean> {
public async meetRequirements(context: ExtensionContext): Promise<boolean> {
const hasInited: boolean | undefined = context.globalState.get(leetcodeHasInited);
if (!hasInited) {
await this.removeOldCache();
}
if (this.nodeExecutable !== "node") {
if (!await fse.pathExists(this.nodeExecutable)) {
throw new Error(`The Node.js executable does not exist on path ${this.nodeExecutable}`);
@@ -69,10 +66,13 @@ class LeetCodeExecutor implements Disposable {
for (const plugin of supportedPlugins) {
try { // Check plugin
await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-e", plugin]);
} catch (error) { // Download plugin and activate
} catch (error) { // Remove old cache that may cause the error download plugin and activate
await this.removeOldCache();
await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-i", plugin]);
}
}
// Set the global state HasInited true to skip delete old cache after init
context.globalState.update(leetcodeHasInited, true);
return true;
}

@@ -85,33 +85,65 @@ class LeetCodeExecutor implements Disposable {
}

public async signOut(): Promise<string> {
return await await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user", "-L"]);
return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user", "-L"]);
}

public async listProblems(showLocked: boolean): Promise<string> {
return await this.executeCommandEx(this.nodeExecutable, showLocked ?
[await this.getLeetCodeBinaryPath(), "list"] :
[await this.getLeetCodeBinaryPath(), "list", "-q", "L"],
);
public async listProblems(showLocked: boolean, needTranslation: boolean): Promise<string> {
const cmd: string[] = [await this.getLeetCodeBinaryPath(), "list"];
if (!needTranslation) {
cmd.push("-T"); // use -T to prevent translation
}
if (!showLocked) {
cmd.push("-q");
cmd.push("L");
}
return await this.executeCommandEx(this.nodeExecutable, cmd);
}

public async showProblem(problemNode: IProblem, language: string, filePath: string, detailed: boolean = false): Promise<void> {
const templateType: string = detailed ? "-cx" : "-c";
public async showProblem(problemNode: IProblem, language: string, filePath: string, showDescriptionInComment: boolean = false, needTranslation: boolean): Promise<void> {
const templateType: string = showDescriptionInComment ? "-cx" : "-c";
const cmd: string[] = [await this.getLeetCodeBinaryPath(), "show", problemNode.id, templateType, "-l", language];

if (!needTranslation) {
cmd.push("-T"); // use -T to force English version
}

if (!await fse.pathExists(filePath)) {
await fse.createFile(filePath);
const codeTemplate: string = await this.executeCommandWithProgressEx("Fetching problem data...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNode.id, templateType, "-l", language]);
const codeTemplate: string = await this.executeCommandWithProgressEx("Fetching problem data...", this.nodeExecutable, cmd);
await fse.writeFile(filePath, codeTemplate);
}
}

public async showSolution(input: string, language: string): Promise<string> {
const solution: string = await this.executeCommandWithProgressEx("Fetching top voted solution from discussions...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", input, "--solution", "-l", language]);
/**
* This function returns solution of a problem identified by input
*
* @remarks
* Even though this function takes the needTranslation flag, it is important to note
* that as of vsc-leetcode-cli 2.8.0, leetcode-cli doesn't support querying solution
* on CN endpoint yet. So this flag doesn't have any effect right now.
*
* @param input - parameter to pass to cli that can identify a problem
* @param language - the source code language of the solution desired
* @param needTranslation - whether or not to use endPoint translation on solution query
* @returns promise of the solution string
*/
public async showSolution(input: string, language: string, needTranslation: boolean): Promise<string> {
// solution don't support translation
const cmd: string[] = [await this.getLeetCodeBinaryPath(), "show", input, "--solution", "-l", language];
if (!needTranslation) {
cmd.push("-T");
}
const solution: string = await this.executeCommandWithProgressEx("Fetching top voted solution from discussions...", this.nodeExecutable, cmd);
return solution;
}

public async getDescription(problemNodeId: string): Promise<string> {
return await this.executeCommandWithProgressEx("Fetching problem description...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNodeId, "-x"]);
public async getDescription(problemNodeId: string, needTranslation: boolean): Promise<string> {
const cmd: string[] = [await this.getLeetCodeBinaryPath(), "show", problemNodeId, "-x"];
if (!needTranslation) {
cmd.push("-T");
}
return await this.executeCommandWithProgressEx("Fetching problem description...", this.nodeExecutable, cmd);
}

public async listSessions(): Promise<string> {
@@ -168,7 +200,7 @@ class LeetCodeExecutor implements Disposable {

public async getCompaniesAndTags(): Promise<{ companies: { [key: string]: string[] }, tags: { [key: string]: string[] } }> {
// preprocess the plugin source
const companiesTagsPath: string = path.join(await leetCodeExecutor.getLeetCodeRootPath(), "lib", "plugins", "company.js");
const companiesTagsPath: string = path.join(this.leetCodeRootPath, "lib", "plugins", "company.js");
const companiesTagsSrc: string = (await fse.readFile(companiesTagsPath, "utf8")).replace(
"module.exports = plugin",
"module.exports = { COMPONIES, TAGS }",
@@ -203,6 +235,14 @@ class LeetCodeExecutor implements Disposable {
}
return await executeCommandWithProgress(message, command, args, options);
}

private async removeOldCache(): Promise<void> {
const oldPath: string = path.join(os.homedir(), ".lc");
if (await fse.pathExists(oldPath)) {
await fse.remove(oldPath);
}
}

}

export const leetCodeExecutor: LeetCodeExecutor = new LeetCodeExecutor();
191 changes: 129 additions & 62 deletions src/leetCodeManager.ts
Original file line number Diff line number Diff line change
@@ -6,19 +6,26 @@ import { EventEmitter } from "events";
import * as vscode from "vscode";
import { leetCodeChannel } from "./leetCodeChannel";
import { leetCodeExecutor } from "./leetCodeExecutor";
import { UserStatus } from "./shared";
import { Endpoint, IQuickItemEx, loginArgsMapping, urls, urlsCn, UserStatus } from "./shared";
import { createEnvOption } from "./utils/cpUtils";
import { DialogType, promptForOpenOutputChannel } from "./utils/uiUtils";
import { DialogType, openUrl, promptForOpenOutputChannel } from "./utils/uiUtils";
import * as wsl from "./utils/wslUtils";
import { getLeetCodeEndpoint } from "./commands/plugin";
import { globalState } from "./globalState";
import { queryUserData } from "./request/query-user-data";
import { parseQuery } from "./utils/toolUtils";

class LeetCodeManager extends EventEmitter {
private currentUser: string | undefined;
private userStatus: UserStatus;
private readonly successRegex: RegExp = /(?:.*)Successfully .*login as (.*)/i;
private readonly failRegex: RegExp = /.*\[ERROR\].*/i;

constructor() {
super();
this.currentUser = undefined;
this.userStatus = UserStatus.SignedOut;
this.handleUriSignIn = this.handleUriSignIn.bind(this);
}

public async getLoginStatus(): Promise<void> {
@@ -29,77 +36,90 @@ class LeetCodeManager extends EventEmitter {
} catch (error) {
this.currentUser = undefined;
this.userStatus = UserStatus.SignedOut;
globalState.removeAll();
} finally {
this.emit("statusChanged");
}
}

public async signIn(isByCookie: boolean = false): Promise<void> {
const loginArg: string = "-l";
const cookieArg: string = "-c";
const commandArg: string = isByCookie ? cookieArg : loginArg;
const inMessage: string = isByCookie ? "sign in by cookie" : "sign in";
private async updateUserStatusWithCookie(cookie: string): Promise<void> {
globalState.setCookie(cookie);
const data = await queryUserData();
globalState.setUserStatus(data);
await this.setCookieToCli(cookie, data.username);
if (data.username) {
vscode.window.showInformationMessage(`Successfully ${data.username}.`);
this.currentUser = data.username;
this.userStatus = UserStatus.SignedIn;
this.emit("statusChanged");
}
}

public async handleUriSignIn(uri: vscode.Uri): Promise<void> {
try {
const userName: string | undefined = await new Promise(async (resolve: (res: string | undefined) => void, reject: (e: Error) => void): Promise<void> => {
let result: string = "";

const leetCodeBinaryPath: string = await leetCodeExecutor.getLeetCodeBinaryPath();

const childProc: cp.ChildProcess = wsl.useWsl()
? cp.spawn("wsl", [leetCodeExecutor.node, leetCodeBinaryPath, "user", commandArg], { shell: true })
: cp.spawn(leetCodeExecutor.node, [leetCodeBinaryPath, "user", commandArg], {
shell: true,
env: createEnvOption(),
});

childProc.stdout.on("data", (data: string | Buffer) => {
data = data.toString();
result = result.concat(data);
leetCodeChannel.append(data);
});

childProc.stderr.on("data", (data: string | Buffer) => leetCodeChannel.append(data.toString()));

childProc.on("error", reject);
const name: string | undefined = await vscode.window.showInputBox({
prompt: "Enter username or E-mail.",
validateInput: (s: string): string | undefined => s && s.trim() ? undefined : "The input must not be empty",
});
if (!name) {
childProc.kill();
return resolve(undefined);
}
childProc.stdin.write(`${name}\n`);
const pwd: string | undefined = await vscode.window.showInputBox({
prompt: isByCookie ? "Enter cookie" : "Enter password.",
password: true,
validateInput: (s: string): string | undefined => s ? undefined : isByCookie ? "Cookie must not be empty" : "Password must not be empty",
});
if (!pwd) {
childProc.kill();
return resolve(undefined);
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async (progress: vscode.Progress<{}>) => {
progress.report({ message: "Fetching user data..." });
const queryParams = parseQuery(uri.query);
const cookie = queryParams["cookie"];
if (!cookie) {
promptForOpenOutputChannel(`Failed to get cookie. Please log in again`, DialogType.error);
return;
}
childProc.stdin.write(`${pwd}\n`);
childProc.stdin.end();
childProc.on("close", () => {
const match: RegExpMatchArray | null = result.match(/(?:.*) Successfully (login|cookie login) as (.*)/i);
if (match && match[2]) {
resolve(match[2]);
} else {
reject(new Error(`Failed to ${inMessage}.`));
}
});

await this.updateUserStatusWithCookie(cookie)

});
if (userName) {
vscode.window.showInformationMessage(`Successfully ${inMessage}.`);
this.currentUser = userName;
this.userStatus = UserStatus.SignedIn;
this.emit("statusChanged");
}
} catch (error) {
promptForOpenOutputChannel(`Failed to ${inMessage}. Please open the output channel for details`, DialogType.error);
promptForOpenOutputChannel(`Failed to log in. Please open the output channel for details`, DialogType.error);
}
}

public async handleInputCookieSignIn(): Promise<void> {
const cookie: string | undefined = await vscode.window.showInputBox({
prompt: 'Enter LeetCode Cookie',
password: true,
ignoreFocusOut: true,
validateInput: (s: string): string | undefined =>
s ? undefined : 'Cookie must not be empty',
})

await this.updateUserStatusWithCookie(cookie || '')
}

public async signIn(): Promise<void> {
const picks: Array<IQuickItemEx<string>> = []
picks.push(
{
label: 'Web Authorization',
detail: 'Open browser to authorize login on the website',
value: 'WebAuth',
description: '[Recommended]'
},
{
label: 'LeetCode Cookie',
detail: 'Use LeetCode cookie copied from browser to login',
value: 'Cookie',
}
)

const choice: IQuickItemEx<string> | undefined = await vscode.window.showQuickPick(picks)
if (!choice) {
return
}
const loginMethod: string = choice.value

if (loginMethod === 'WebAuth') {
openUrl(this.getAuthLoginUrl())
return
}

try {
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Fetching user data..." }, async () => {
await this.handleInputCookieSignIn()
});
} catch (error) {
promptForOpenOutputChannel(`Failed to log in. Please open the output channel for details`, DialogType.error);
}
}

public async signOut(): Promise<void> {
@@ -108,6 +128,7 @@ class LeetCodeManager extends EventEmitter {
vscode.window.showInformationMessage("Successfully signed out.");
this.currentUser = undefined;
this.userStatus = UserStatus.SignedOut;
globalState.removeAll();
this.emit("statusChanged");
} catch (error) {
// swallow the error when sign out.
@@ -131,6 +152,52 @@ class LeetCodeManager extends EventEmitter {

return "Unknown";
}

public getAuthLoginUrl(): string {
switch (getLeetCodeEndpoint()) {
case Endpoint.LeetCodeCN:
return urlsCn.authLoginUrl;
case Endpoint.LeetCode:
default:
return urls.authLoginUrl;
}
}

public setCookieToCli(cookie: string, name: string): Promise<void> {
return new Promise(async (resolve: (res: void) => void, reject: (e: Error) => void) => {
const leetCodeBinaryPath: string = await leetCodeExecutor.getLeetCodeBinaryPath();

const childProc: cp.ChildProcess = wsl.useWsl()
? cp.spawn("wsl", [leetCodeExecutor.node, leetCodeBinaryPath, "user", loginArgsMapping.get("Cookie") ?? ""], {
shell: true,
})
: cp.spawn(leetCodeExecutor.node, [leetCodeBinaryPath, "user", loginArgsMapping.get("Cookie") ?? ""], {
shell: true,
env: createEnvOption(),
});

childProc.stdout?.on("data", async (data: string | Buffer) => {
data = data.toString();
leetCodeChannel.append(data);
const successMatch: RegExpMatchArray | null = data.match(this.successRegex);
if (successMatch && successMatch[1]) {
childProc.stdin?.end();
return resolve();
} else if (data.match(this.failRegex)) {
childProc.stdin?.end();
return reject(new Error("Faile to login"));
} else if (data.match(/login: /)) {
childProc.stdin?.write(`${name}\n`);
} else if (data.match(/cookie: /)) {
childProc.stdin?.write(`${cookie}\n`);
}
});

childProc.stderr?.on("data", (data: string | Buffer) => leetCodeChannel.append(data.toString()));

childProc.on("error", reject);
});
}
}

export const leetCodeManager: LeetCodeManager = new LeetCodeManager();
25 changes: 25 additions & 0 deletions src/request/query-user-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { UserDataType } from "../globalState";
import { getUrl } from "../shared";
import { LcAxios } from "../utils/httpUtils";

const graphqlStr = `
query globalData {
userStatus {
isPremium
isVerified
username
avatar
isSignedIn
}
}
`;

export const queryUserData = async (): Promise<UserDataType> => {
return LcAxios(getUrl("userGraphql"), {
method: "POST",
data: {
query: graphqlStr,
variables: {},
},
}).then((res) => res.data.data.userStatus);
};
68 changes: 63 additions & 5 deletions src/shared.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,13 @@ export enum UserStatus {
SignedOut = 2,
}

export const loginArgsMapping: Map<string, string> = new Map([
["LeetCode", "-l"],
["Cookie", "-c"],
["GitHub", "-g"],
["LinkedIn", "-i"],
]);

export const languages: string[] = [
"bash",
"c",
@@ -29,6 +36,7 @@ export const languages: string[] = [
"rust",
"scala",
"swift",
"typescript",
];

export const langExt: Map<string, string> = new Map([
@@ -48,12 +56,14 @@ export const langExt: Map<string, string> = new Map([
["rust", "rs"],
["scala", "scala"],
["swift", "swift"],
["typescript", "ts"],
]);

export enum ProblemState {
AC = 1,
NotAC = 2,
Unknown = 3,
Locked = 4,
}

export enum Endpoint {
@@ -93,8 +103,56 @@ export enum Category {
Favorite = "Favorite",
}

export const supportedPlugins: string[] = [
"company",
"solution.discuss",
"leetcode.cn",
];
export const supportedPlugins: string[] = ["company", "solution.discuss", "leetcode.cn"];

export enum DescriptionConfiguration {
InWebView = "In Webview",
InFileComment = "In File Comment",
Both = "Both",
None = "None",
}

export const leetcodeHasInited: string = "leetcode.hasInited";

export enum SortingStrategy {
None = "None",
AcceptanceRateAsc = "Acceptance Rate (Ascending)",
AcceptanceRateDesc = "Acceptance Rate (Descending)",
FrequencyAsc = "Frequency (Ascending)",
FrequencyDesc = "Frequency (Descending)",
}

export const PREMIUM_URL_CN = "https://leetcode.cn/premium-payment/?source=vscode";
export const PREMIUM_URL_GLOBAL = "https://leetcode.com/subscribe/?ref=lp_pl&source=vscode";

const protocol = vscode.env.appName.includes('Insiders') ? "vscode-insiders" : "vscode"

export const urls = {
// base urls
base: "https://leetcode.com",
graphql: "https://leetcode.com/graphql",
userGraphql: "https://leetcode.com/graphql",
login: "https://leetcode.com/accounts/login/",
authLoginUrl: `https://leetcode.com/authorize-login/${protocol}/?path=leetcode.vscode-leetcode`,
};

export const urlsCn = {
// base urls
base: "https://leetcode.cn",
graphql: "https://leetcode.cn/graphql",
userGraphql: "https://leetcode.cn/graphql/",
login: "https://leetcode.cn/accounts/login/",
authLoginUrl: `https://leetcode.cn/authorize-login/${protocol}/?path=leetcode.vscode-leetcode`,
};

export const getUrl = (key: string) => {
const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode");
const point = leetCodeConfig.get<string>("endpoint", Endpoint.LeetCode);
switch (point) {
case Endpoint.LeetCodeCN:
return urlsCn[key];
case Endpoint.LeetCode:
default:
return urls[key];
}
};
6 changes: 3 additions & 3 deletions src/utils/cpUtils.ts
Original file line number Diff line number Diff line change
@@ -15,13 +15,13 @@ export async function executeCommand(command: string, args: string[], options: c

const childProc: cp.ChildProcess = cp.spawn(command, args, { ...options, env: createEnvOption() });

childProc.stdout.on("data", (data: string | Buffer) => {
childProc.stdout?.on("data", (data: string | Buffer) => {
data = data.toString();
result = result.concat(data);
leetCodeChannel.append(data);
});

childProc.stderr.on("data", (data: string | Buffer) => leetCodeChannel.append(data.toString()));
childProc.stderr?.on("data", (data: string | Buffer) => leetCodeChannel.append(data.toString()));

childProc.on("error", reject);

@@ -42,7 +42,7 @@ export async function executeCommand(command: string, args: string[], options: c
export async function executeCommandWithProgress(message: string, command: string, args: string[], options: cp.SpawnOptions = { shell: true }): Promise<string> {
let result: string = "";
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, async (p: vscode.Progress<{}>) => {
return new Promise(async (resolve: () => void, reject: (e: Error) => void): Promise<void> => {
return new Promise<void>(async (resolve: () => void, reject: (e: Error) => void): Promise<void> => {
p.report({ message });
try {
result = await executeCommand(command, args, options);
28 changes: 28 additions & 0 deletions src/utils/httpUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import axios, { AxiosRequestConfig, AxiosPromise } from "axios";
import { omit } from "lodash";
import { globalState } from "../globalState";
import { DialogType, promptForOpenOutputChannel } from "./uiUtils";

const referer = "vscode-lc-extension";

export function LcAxios<T = any>(path: string, settings?: AxiosRequestConfig): AxiosPromise<T> {
const cookie = globalState.getCookie();
if (!cookie) {
promptForOpenOutputChannel(
`Failed to obtain the cookie. Please log in again.`,
DialogType.error
);
return Promise.reject("Failed to obtain the cookie.");
}
return axios(path, {
headers: {
referer,
"content-type": "application/json",
cookie,
...(settings && settings.headers),
},
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFToken",
...(settings && omit(settings, "headers")),
});
}
48 changes: 48 additions & 0 deletions src/utils/settingUtils.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
// Licensed under the MIT license.

import { workspace, WorkspaceConfiguration } from "vscode";
import { DescriptionConfiguration } from "../shared";

export function getWorkspaceConfiguration(): WorkspaceConfiguration {
return workspace.getConfiguration("leetcode");
@@ -18,3 +19,50 @@ export function getWorkspaceFolder(): string {
export function getEditorShortcuts(): string[] {
return getWorkspaceConfiguration().get<string[]>("editor.shortcuts", ["submit", "test"]);
}

export function hasStarShortcut(): boolean {
const shortcuts: string[] = getWorkspaceConfiguration().get<string[]>("editor.shortcuts", ["submit", "test"]);
return shortcuts.indexOf("star") >= 0;
}

export function shouldUseEndpointTranslation(): boolean {
return getWorkspaceConfiguration().get<boolean>("useEndpointTranslation", true);
}

export function getDescriptionConfiguration(): IDescriptionConfiguration {
const setting: string = getWorkspaceConfiguration().get<string>("showDescription", DescriptionConfiguration.InWebView);
const config: IDescriptionConfiguration = {
showInComment: false,
showInWebview: true,
};
switch (setting) {
case DescriptionConfiguration.Both:
config.showInComment = true;
config.showInWebview = true;
break;
case DescriptionConfiguration.None:
config.showInComment = false;
config.showInWebview = false;
break;
case DescriptionConfiguration.InFileComment:
config.showInComment = true;
config.showInWebview = false;
break;
case DescriptionConfiguration.InWebView:
config.showInComment = false;
config.showInWebview = true;
break;
}

// To be compatible with the deprecated setting:
if (getWorkspaceConfiguration().get<boolean>("showCommentDescription")) {
config.showInComment = true;
}

return config;
}

export interface IDescriptionConfiguration {
showInComment: boolean;
showInWebview: boolean;
}
26 changes: 26 additions & 0 deletions src/utils/toolUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

export function parseQuery(query: string): { [key: string]: string } {
const queryObject: { [key: string]: string } = {};

if (!query) {
return queryObject;
}

const keyValuePairs = query.split("&");
keyValuePairs.forEach((pair) => {
const firstEqualsIndex = pair.indexOf("=");
if (firstEqualsIndex !== -1) {
const key = pair.substring(0, firstEqualsIndex);
const value = pair.substring(firstEqualsIndex + 1);
queryObject[decodeURIComponent(key)] = decodeURIComponent(value);
} else {
// If no equals sign is found, treat the whole string as key with empty value
queryObject[decodeURIComponent(pair)] = "";
}
});

return queryObject;
}
130 changes: 130 additions & 0 deletions src/utils/trackingUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import * as vscode from "vscode";
import axios from "axios";
import { getLeetCodeEndpoint } from "../commands/plugin";
import { Endpoint } from "../shared";
import { leetCodeManager } from "../leetCodeManager";

const getTimeZone = (): string => {
const endPoint: string = getLeetCodeEndpoint();
if (endPoint === Endpoint.LeetCodeCN) {
return "Asia/Shanghai";
} else {
return "UTC";
}
};

interface IReportData {
event_key: string;
type?: "click" | "expose" | string;
anonymous_id?: string;
tid?: number;
ename?: string; // event name
href?: string;
referer?: string;
extra?: string;
target?: string;
}

interface ITrackData {
reportCache: IReportData[];
isSubmit: boolean;
report: (reportItems: IReportData | IReportData[]) => void;
submitReport: (useSendBeason: boolean) => Promise<void>;
reportUrl: string;
}

const testReportUrl = "https://analysis.lingkou.xyz/i/event";
const prodReportUrl = "https://analysis.leetcode.cn/i/event";

function getReportUrl() {
if (process.env.NODE_ENV === "production") {
return prodReportUrl;
} else {
return testReportUrl;
}
}

const _charStr = "abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+=";

function RandomIndex(min: number, max: number, i: number) {
let index = Math.floor(Math.random() * (max - min + 1) + min);
const numStart = _charStr.length - 10;
if (i === 0 && index >= numStart) {
index = RandomIndex(min, max, i);
}
return index;
}

function getRandomString(len: number) {
const min = 0;
const max = _charStr.length - 1;
let _str = "";
len = len || 15;
for (let i = 0, index; i < len; i++) {
index = RandomIndex(min, max, i);
_str += _charStr[index];
}
return _str;
}

function getAllowReportDataConfig() {
const leetCodeConfig = vscode.workspace.getConfiguration("leetcode");
const allowReportData = !!leetCodeConfig.get<boolean>("allowReportData");
return allowReportData;
}

class TrackData implements ITrackData {
public reportCache: IReportData[] = [];

public isSubmit: boolean = false;

public reportUrl: string = getReportUrl();

private sendTimer: NodeJS.Timeout | undefined;

public report = (reportItems: IReportData | IReportData[]) => {
if (!getAllowReportDataConfig()) return;

this.sendTimer && clearTimeout(this.sendTimer);

if (!Array.isArray(reportItems)) {
reportItems = [reportItems];
}
const randomId = getRandomString(60);
reportItems.forEach((item) => {
this.reportCache.push({
...item,
referer: "vscode",
target: leetCodeManager.getUser() ?? "",
anonymous_id: item.anonymous_id ?? (randomId as string),
});
});
this.sendTimer = setTimeout(this.submitReport, 800);
};

public submitReport = async () => {
if (!getAllowReportDataConfig()) return;
const dataList = JSON.stringify(this.reportCache);

if (!this.reportCache.length || this.isSubmit) {
return;
}
this.reportCache = [];
try {
this.isSubmit = true;
axios.defaults.withCredentials = true;
await axios.post(this.reportUrl, `dataList=${encodeURIComponent(dataList)}`, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"x-timezone": getTimeZone(),
},
});
} catch (e) {
this.reportCache = this.reportCache.concat(JSON.parse(dataList));
} finally {
this.isSubmit = false;
}
};
}

export default new TrackData();
2 changes: 1 addition & 1 deletion src/utils/uiUtils.ts
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ export async function promptForSignIn(): Promise<void> {
break;
case DialogOptions.singUp:
if (getLeetCodeEndpoint()) {
openUrl("https://leetcode-cn.com");
openUrl("https://leetcode.cn");
} else {
openUrl("https://leetcode.com");
}
14 changes: 10 additions & 4 deletions src/utils/workspaceUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) jdneo. All rights reserved.
// Licensed under the MIT license.

import * as fse from "fs-extra";
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
@@ -18,9 +19,9 @@ export async function selectWorkspaceFolder(): Promise<string> {
return workspaceFolderSetting;
}
}
const workspaceFolders: vscode.WorkspaceFolder[] = vscode.workspace.workspaceFolders || [];
let needAsk: boolean = true;
for (const folder of workspaceFolders) {
await fse.ensureDir(workspaceFolderSetting);
for (const folder of vscode.workspace.workspaceFolders || []) {
if (isSubFolder(folder.uri.fsPath, workspaceFolderSetting)) {
needAsk = false;
}
@@ -29,22 +30,26 @@ export async function selectWorkspaceFolder(): Promise<string> {
if (needAsk) {
const choice: string | undefined = await vscode.window.showQuickPick(
[
OpenOption.justOpenFile,
OpenOption.openInCurrentWindow,
OpenOption.openInNewWindow,
OpenOption.addToWorkspace,
],
{ placeHolder: "Select how you would like to open your workspace folder" },
{ placeHolder: "The LeetCode workspace folder is not opened in VS Code, would you like to open it?" },
);

// Todo: generate file first
switch (choice) {
case OpenOption.justOpenFile:
return workspaceFolderSetting;
case OpenOption.openInCurrentWindow:
await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(workspaceFolderSetting), false);
return "";
case OpenOption.openInNewWindow:
await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(workspaceFolderSetting), true);
return "";
case OpenOption.addToWorkspace:
vscode.workspace.updateWorkspaceFolders(workspaceFolders.length, 0, { uri: vscode.Uri.file(workspaceFolderSetting) });
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders?.length ?? 0, 0, { uri: vscode.Uri.file(workspaceFolderSetting) });
break;
default:
return "";
@@ -117,6 +122,7 @@ async function determineLeetCodeFolder(): Promise<string> {
}

enum OpenOption {
justOpenFile = "Just open the problem file",
openInCurrentWindow = "Open in current window",
openInNewWindow = "Open in new window",
addToWorkspace = "Add to workspace",
5 changes: 4 additions & 1 deletion src/utils/wslUtils.ts
Original file line number Diff line number Diff line change
@@ -15,5 +15,8 @@ export async function toWslPath(path: string): Promise<string> {
}

export async function toWinPath(path: string): Promise<string> {
return (await executeCommand("wsl", ["wslpath", "-w", `"${path}"`])).trim();
if (path.startsWith("\\mnt\\")) {
return (await executeCommand("wsl", ["wslpath", "-w", `"${path.replace(/\\/g, "/").substr(0, 6)}"`])).trim() + path.substr(7);
}
return (await executeCommand("wsl", ["wslpath", "-w", "/"])).trim() + path;
}
Loading