-
Notifications
You must be signed in to change notification settings - Fork 664
Enhanced Tree view #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
(from direct import of leetcode-cli company plugin)
Add unknown label to companies and tags
@Vigilans Thank you for contribution. I will look it later. Also noticed that there are some lint errors according to the CI output, would you mind to fix them first? |
The lint errors have been fixed. Note: A default object is used to define // tslint:disable-next-line:typedef
export const IProblemDefault = {
favorite: false,
locked: false,
state: ProblemState.Unknown,
id: "",
name: "",
difficulty: "",
passRate: "",
companies: [] as string[],
tags: [] as string[],
};
export type IProblem = typeof IProblemDefault; The default object is then used to create a new new LeetCodeNode(Object.assign({}, list.IProblemDefault, {
id: "Root", // parent node name
name: "Tag", // current node name
}), false), |
Hi @Vigilans, Is there any reason to add accepted/not accepted categories into the explorer? |
The Not-AC category shows problems which are submitted but not accepted. If you think them not necessary, I will try to revert this commit. |
Hmm, I think it's a little bit redundant somehow. I better approach might be: having an extension setting that specifies if the explorer will only show unresolved problems or all the problems, then this setting will apply to all the categories. To retrieving all accepted codes, this can be another action in the explorer context menu, which is another story. |
This reverts commit 7e00062.
I see. Btw, there are some other changes I plan to contribute, maybe I should open another issue to query you for your opinions_(눈_ 눈」∠)_ |
@taoweicn I will work on this feature later. |
👍 very thankful. |
@Vigilans Contributions are welcome! 👍 Yes Let's open issues and discuss the implementation there first before we create a PR. 😄 |
@Vigilans Could you please send another PR about hiding solved problems? A PR contains several issue fixes will make it too large to review |
@jdneo The |
src/commands/list.ts
Outdated
tags: [] as string[], | ||
}; | ||
|
||
export type IProblem = typeof IProblemDefault; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to put the IProblem
definition and IProblemDefault
to other places instead of in this file.
Let's put them into shared.ts
, I'll refactor this part later.
src/leetCodeExecutor.ts
Outdated
@@ -2,6 +2,7 @@ | |||
// Licensed under the MIT license. | |||
|
|||
import * as cp from "child_process"; | |||
import * as fs from "fs"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use fs-extra
src/leetCodeExecutor.ts
Outdated
public async getCompaniesAndTags(): Promise<{ companies: { [key: string]: string[] }, tags: { [key: string]: string[] } }> { | ||
// preprocess the plugin source | ||
const componiesTagsPath: string = path.join(await leetCodeExecutor.getLeetCodeRootPath(), "lib", "plugins", "company.js"); | ||
const componiesTagsSrc: string = (await fs.readFileSync(componiesTagsPath, "utf8")).replace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use readFile
from fs-extra
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something went wrong with my usage. fs-extra
is correct.
src/leetCodeExecutor.ts
Outdated
"module.exports = { COMPONIES, TAGS }", | ||
); | ||
// require plugin from modified string | ||
const requireFromString: (src: string, path: string) => any = require("require-from-string"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we import the module?
For example, npm i --save-dev @types/require-from-string
Then import it instead of using require
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the definitelyTyped
indeed provides type for require-from-string
.
import * as requireFromString from "require-from-string";
successfully worked.
src/leetCodeExplorer.ts
Outdated
@@ -7,6 +7,8 @@ import * as list from "./commands/list"; | |||
import { leetCodeManager } from "./leetCodeManager"; | |||
import { ProblemState } from "./shared"; | |||
|
|||
export type Category = "Difficulty" | "Tag" | "Company"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use enum instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The LeetCodeTreeDataProvider.treeData
needs the type semantic from Category
to preserve the type hint, whereas enum just provides the value semantic.
Without the string union type, this line won't get proper type hint:
const problems: list.IProblem[] | undefined = this.treeData[parent].get(subCategory);
this.treeData[parent]
will return any
type.
If we change Category
to enum type, then the above line should be rewritten to:
this.treeData[parent.toString() as "Difficulty" | "Tag" | "Company"]
which falls back to where it starts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried this:
export enum Category {
Difficulty = "Difficulty",
Tag = "Tag",
Company = "Company",
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After trying, Category.Tag.toString()
only returns type string
.
There is no one-line way to get the type hint here, reference:
https://stackoverflow.com/questions/52370544/parse-string-as-typescript-enum/
https://stackoverflow.com/questions/52393730/typescript-string-literal-union-type-from-enum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to call .toString()
here.
Whenever you want to use the Category value as a string, just using Category.xxxx
.
By doing this, you will get the value as a string meanwhile keep the typedef.
I tried using enum here and everything works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make the Category
enum type:
export enum Category {
Difficulty = "Difficulty",
Tag = "Tag",
Company = "Company",
}
Then rewrite line 147 this way:
const categories: Map<Category, string[]> = new Map<Category, string[]>([
[Category.Difficulty, [problem.difficulty]],
[Category.Tag, problem.tags],
[Category.Company, problem.companies],
]);
Follows the iteration:
for (const [parent, children] of categories) {
for (let subCategory of children) {
Here, typeof parent
is Category
:
Continues the line 156:
const problems: list.IProblem[] | undefined = this.treeData[parent].get(subCategory);
Since typeof parent
is Category
and this.treeData
's key type is string
, get
's type could not be deduced from the usage, and there's no way to use Category.xxxx
here:
Meanwhile, we couldn't change this.treeData
type to Map<Category, Map<string, list.IProblem[]>>
since there is a field Favorite
which doesn't processed as Category
and its value type is list.IProblem[]
.
If you insist to use enum here, a workaround could be:
private treeData: {
[Category.Difficulty]: Map<string, list.IProblem[]>,
[Category.Tag]: Map<string, list.IProblem[]>,
[Category.Company]: Map<string, list.IProblem[]>,
Favorite: list.IProblem[],
};
Now get
's type could be deduced properly:
Yet I think it complicates what could have been an easy and elegant solution in Typescript.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to write more hard-coded strings if using string union here while using enum can get rid of this. That's the reason I prefer using enum here: make the code easier to maintain in the future.
There are many ways to get rid of the special field Favorite
. One simple way to do a simple check before getting the map. TS will help us to infer the type during compilation.
src/leetCodeExplorer.ts
Outdated
Company: new Map(), | ||
Favorite: [], | ||
}; | ||
for (const problem of await list.listProblems()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking that we can filter the accepted problems in the method list.listProblems()
. If hideSolved === true
then only return unaccepted problems.
By doing this, we decouple the this.leetCodeConfig
with the explorer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, Favorite
group should show all starred problems no matter whether it's accepted or not.
Maybe it could be checked this way in list.listProblems()
:
!problem.favorite && problem.state === ProblemState.AC && hideSolved
-> continue
The flaw is that the favorite checking logic is duplicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, ok, just keep it there first.
src/leetCodeExplorer.ts
Outdated
continue; | ||
} | ||
// Add problems according to category | ||
const categories: Array<[Category, string[]]> = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Map here?
use index to represent the parent-children
relationship is confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use Map here this way:
const categories: Map<Category, string[]> = new Map<Category, string[]>([
["Difficulty", [problem.difficulty]],
["Tag", problem.tags],
["Company", problem.companies],
]);
Btw, a better way would be to use Object.entries
here, but it is not supported by es6, which is specified by tsconfig.json
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use Map here.
src/leetCodeExplorer.ts
Outdated
for (const [parent, children] of categories) { | ||
for (let subCategory of children) { | ||
// map 'first-second' to 'First Second' | ||
subCategory = subCategory.split("-").map((c: string) => c[0].toUpperCase() + c.slice(1)).join(" "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first-second
is not that bad.
Cuz current logic could handle a-b
but not a-b-c
Just leave first-second
as it is for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does a-b-c
refer to? The code here could transform breadth-first-search
to Breadth First Search
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, it's fine.
src/leetCodeExplorer.ts
Outdated
@@ -64,57 +73,100 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider<LeetCod | |||
} | |||
|
|||
const idPrefix: number = Date.now(); | |||
return { | |||
return { // element.id -> parent node name, element.name -> node name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we save parent node name into element.id
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some field to store the parent node name when it is not problem node...since in previous commits you simply set node.id
and node.name
the same value, I decided to reuse the id
field to represent some other value.
The reasons for the necessity of parent node name are:
- We should be able to retrieve data from the
LeetCodeTreeDataProvider.treeData
. - One problem will now be added as
LeetCodeNode
at least 3 times(inDifficulty
,Tag
andCompany
), we should be able to distinguish them, hence the following naming rule:
id: `${idPrefix}.${element.id}.${element.name}`,
(idPrefix = Date.now()
just returns same value throughout initialization process)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could add a filed in the LeetCodeNode
called 'parentName' to store the parent node info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will review later
Merged with some refactoring. @Vigilans Thank you for your contribution. The idea is very inspiring! |
Introduction
This PR restructures the left side panel's tree view to support following functions:
Favorite
view)Details
leetcode-cli
company plugin.Unknown
.leetcode.hideSolved
setting, which defaults to false.Demonstration
Difficulty, tag, company or favorite:

Problems by tag:

Problems by company:

Favorite problems:
