Skip to content

Release/v0.5.3 #16

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 108 additions & 74 deletions leetcode-tui-core/src/content/question.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ use crate::{emit, utils::Paginate};
use fuzzy_matcher::skim::SkimMatcherV2;
use fuzzy_matcher::FuzzyMatcher;
use html2md::parse_html;
use leetcode_core::errors::AppResult;
use leetcode_core::graphql::query::{daily_coding_challenge, RunOrSubmitCodeCheckResult};
use leetcode_core::types::language::Language;
use leetcode_core::types::run_submit_response::display::CustomDisplay;
use leetcode_core::types::run_submit_response::ParsedResponse;
use leetcode_core::{
GQLLeetcodeRequest, QuestionContentRequest, RunCodeRequest, SubmitCodeRequest,
};
use leetcode_tui_config::log;
use leetcode_tui_db::enums::Difficulty;
use leetcode_tui_db::{DbQuestion, DbTopic};
use leetcode_tui_shared::layout::Window;
pub(crate) use sol_dir::init;
use sol_dir::SOLUTION_FILE_MANAGER;
use sol_dir::{SolutionFile, SolutionFileManager, SOLUTION_FILE_MANAGER};
use stats::Stats;
use std::rc::Rc;

Expand All @@ -25,6 +28,7 @@ pub struct Questions {
ques_haystack: Vec<Rc<DbQuestion>>,
needle: Option<String>,
matcher: SkimMatcherV2,
difficulty: Option<Difficulty>,
show_stats: bool,
}

Expand All @@ -36,6 +40,7 @@ impl Default for Questions {
ques_haystack: vec![],
matcher: Default::default(),
show_stats: Default::default(),
difficulty: Default::default(),
}
}
}
Expand Down Expand Up @@ -69,7 +74,7 @@ impl Questions {
return true;
} else {
emit!(Popup(
"not",
"Adhoc question not found",
vec![format!(
"Question not found with id={}, title={}",
question.id, question.title
Expand Down Expand Up @@ -124,93 +129,67 @@ impl Questions {
self._run_solution(true)
}

pub fn get_solution_language_list<'a>(
question_id: String,
) -> Result<Vec<Language>, crate::errors::CoreError> {
SolutionFileManager::get_instance()
.get_available_languages(question_id.as_str())
.map(|x| x.into_iter().map(|x| x.clone()).collect())
}

fn _run_solution(&self, is_submit: bool) -> bool {
if let Some(_hovered) = self.hovered() {
let mut cloned_quest = _hovered.as_ref().clone();
let id = _hovered.id.to_string();
if let Ok(lang_refs) = SOLUTION_FILE_MANAGER
.get()
.unwrap()
.read()
.unwrap()
.get_available_languages(id.as_str())
.emit_if_error()
{

// get solution language list
if let Ok(lang_refs) = Self::get_solution_language_list(id.clone()).emit_if_error() {
let cloned_langs = lang_refs.iter().map(|v| v.to_string()).collect();
tokio::spawn(async move {
// popup that gets the input for language to run solution in
if let Some(selected_lang) =
emit!(SelectPopup("Available solutions in", cloned_langs)).await
{
let selected_sol_file = SOLUTION_FILE_MANAGER
.get()
.unwrap()
.read()
.unwrap()
// fetch solution content
let selected_sol_file = SolutionFileManager::get_instance()
.get_solution_file(id.as_str(), selected_lang)
.cloned();

if let Ok(f) = selected_sol_file.emit_if_error() {
if let Ok(contents) = f.read_contents().await.emit_if_error() {
let lang = f.language;
let request = if is_submit {
SubmitCodeRequest::new(
lang,
f.question_id,
contents,
f.title_slug,
)
.poll_check_response()
.await
} else {
let mut run_code_req = RunCodeRequest::new(
lang,
None,
f.question_id,
contents,
f.title_slug,
);
if let Err(e) = run_code_req
.set_sample_test_cases_if_none()
.await
.emit_if_error()
{
log::info!(
"error while setting the sample testcase list {}",
e
);
return;
} else {
run_code_req.poll_check_response().await
}
};

if let Ok(response) = request.emit_if_error() {
if let Ok(update_result) =
cloned_quest.mark_attempted().emit_if_error()
{
// when solution is just run against sample cases
if update_result.is_some() {
// fetches latest result from db
emit!(QuestionUpdate);
}
let request = if is_submit {
Self::send_http_request_to_submit_solved_question(&f).await
} else {
Self::send_http_request_to_run_solved_question(&f).await
};

if let Ok(response) = request.emit_if_error() {
if let Ok(update_result) =
cloned_quest.mark_attempted().emit_if_error()
{
// when solution is just run against sample cases
if update_result.is_some() {
// fetches latest result from db
emit!(QuestionUpdate);
}
}

if is_submit {
let is_submission_accepted =
matches!(response, ParsedResponse::SubmitAccepted(..));
if is_submission_accepted {
if let Ok(update_result) =
cloned_quest.mark_accepted().emit_if_error()
{
// when solution is accepted
if update_result.is_some() {
// fetches latest result from db
emit!(QuestionUpdate);
}
};
}
if is_submit {
let is_submission_accepted =
matches!(response, ParsedResponse::SubmitAccepted(..));
if is_submission_accepted {
if let Ok(update_result) =
cloned_quest.mark_accepted().emit_if_error()
{
// when solution is accepted
if update_result.is_some() {
// fetches latest result from db
emit!(QuestionUpdate);
}
};
}
emit!(Popup(response.get_display_lines()));
}

emit!(Popup(response.get_display_lines()));
}
}
}
Expand All @@ -220,6 +199,38 @@ impl Questions {
false
}

async fn send_http_request_to_submit_solved_question(
f: &SolutionFile,
) -> AppResult<ParsedResponse> {
let solution_text = f.read_contents().await.expect("Cannot read file contents");
SubmitCodeRequest::new(
f.language.clone(),
f.question_id.clone(),
solution_text,
f.title_slug.clone(),
)
.poll_check_response()
.await
}

async fn send_http_request_to_run_solved_question(
f: &SolutionFile,
) -> AppResult<ParsedResponse> {
let solution_text = f.read_contents().await.expect("Cannot read file contents");
let mut run_code_req = RunCodeRequest::new(
f.language.clone(),
None,
f.question_id.clone(),
solution_text,
f.title_slug.clone(),
);
run_code_req
.set_sample_test_cases_if_none()
.await
.expect("error while setting the sample testcase list");
run_code_req.poll_check_response().await
}

pub fn solve_for_language(&self) -> bool {
if let Some(_hovered) = self.hovered() {
let slug = _hovered.title_slug.clone();
Expand Down Expand Up @@ -280,6 +291,7 @@ impl Questions {

pub fn set_questions(&mut self, questions: Vec<DbQuestion>) {
self.ques_haystack = questions.into_iter().map(Rc::new).collect();
self.ques_haystack.sort();
self.filter_questions();
}

Expand Down Expand Up @@ -335,7 +347,6 @@ impl Questions {
}

fn filter_questions(&mut self) {
self.ques_haystack.sort();
let fil_quests = if let Some(needle) = self.needle.as_ref() {
let quests: Vec<Rc<DbQuestion>> = self
.ques_haystack
Expand Down Expand Up @@ -364,6 +375,29 @@ impl Questions {
};
self.paginate.update_list(fil_quests);
}

pub fn toggle_difficulty(&mut self) -> bool {
match self.difficulty {
Some(Difficulty::Easy) => self.difficulty = Some(Difficulty::Medium),
Some(Difficulty::Medium) => self.difficulty = Some(Difficulty::Hard),
Some(Difficulty::Hard) => self.difficulty = None,
None => self.difficulty = Some(Difficulty::Easy),
}

let fil_quests: Vec<Rc<DbQuestion>> = self
.ques_haystack
.iter()
.filter(|q| {
self.difficulty
.as_ref()
.map_or(true, |d| d == &q.get_difficulty())
})
.cloned()
.collect();

self.paginate.update_list(fil_quests);
return true;
}
}

impl Questions {
Expand Down
13 changes: 11 additions & 2 deletions leetcode-tui-core/src/content/question/sol_dir.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use indexmap::IndexSet;
use std::{collections::HashMap, hash::Hash, path::PathBuf, sync::RwLock};
use std::{
collections::HashMap,
hash::Hash,
path::PathBuf,
sync::{RwLock, RwLockReadGuard},
};
use tokio::fs::read_to_string;

use leetcode_tui_config::CONFIG;
use leetcode_core::types::language::Language;
use leetcode_tui_config::CONFIG;
use regex::Regex;
use std::sync::OnceLock;

Expand Down Expand Up @@ -43,6 +48,10 @@ pub struct SolutionFileManager {
}

impl SolutionFileManager {
pub fn get_instance() -> RwLockReadGuard<'static, Self> {
return SOLUTION_FILE_MANAGER.get().unwrap().read().unwrap();
}

fn add_solution_file(&mut self, file: SolutionFile) {
self.id_language_map
.entry(file.question_id.clone())
Expand Down
22 changes: 11 additions & 11 deletions leetcode-tui-core/src/content/question/stats.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use leetcode_tui_config::CONFIG;
use leetcode_tui_db::DbQuestion;
use leetcode_tui_db::{enums::Difficulty, DbQuestion};
use ratatui::style::Style;
use std::{fmt::Display, rc::Rc};

Expand Down Expand Up @@ -97,35 +97,35 @@ impl<'a> Stats<'a> {
}

pub fn get_easy_count(&self) -> usize {
self.get_diff_count("Easy")
self.get_diff_count(Difficulty::Easy)
}

pub fn get_medium_count(&self) -> usize {
self.get_diff_count("Medium")
self.get_diff_count(Difficulty::Medium)
}

pub fn get_hard_count(&self) -> usize {
self.get_diff_count("Hard")
self.get_diff_count(Difficulty::Hard)
}

pub fn get_easy_accepted(&self) -> usize {
self.get_diff_accepted("ac", "Easy")
self.get_diff_accepted("ac", Difficulty::Easy)
}

pub fn get_medium_accepted(&self) -> usize {
self.get_diff_accepted("ac", "Medium")
self.get_diff_accepted("ac", Difficulty::Medium)
}

pub fn get_hard_accepted(&self) -> usize {
self.get_diff_accepted("ac", "Hard")
self.get_diff_accepted("ac", Difficulty::Hard)
}

pub fn get_diff_accepted(&self, status: &str, difficulty: &str) -> usize {
pub fn get_diff_accepted(&self, status: &str, difficulty: Difficulty) -> usize {
self.qm
.iter()
.filter(|q| {
if let Some(st) = &q.status {
st.as_str() == status && difficulty == q.difficulty.as_str()
st.as_str() == status && difficulty == q.get_difficulty()
} else {
false
}
Expand All @@ -146,10 +146,10 @@ impl<'a> Stats<'a> {
.count()
}

fn get_diff_count(&self, difficulty: &str) -> usize {
fn get_diff_count(&self, difficulty: Difficulty) -> usize {
self.qm
.iter()
.filter(|q| q.difficulty.as_str() == difficulty)
.filter(|q| q.get_difficulty() == difficulty)
.count()
}
}
8 changes: 5 additions & 3 deletions leetcode-tui-core/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ impl Default for Help {
let mut help = Self {
state: TableState::default(),
items: vec![
vec!["t", "Move to Next Topic"],
vec!["T", "Move to Previous Topic"],
vec!["Ctrl+s", "Show/Hide topic stats"],
vec!["j/Down", "Move to Next Question"],
vec!["k/Up", "Move to Previous Question"],
vec!["r", "Move to Random Question"],
vec!["Enter", "Read Question/Selection"],
vec!["e", "Open Editor"],
vec!["R", "Run Solution"],
vec!["s", "Submit Solution"],
vec!["t", "Move to Next Topic"],
vec!["T", "Move to Previous Topic"],
vec!["Ctrl+s", "Show/Hide topic stats"],
vec!["Tab", "Toggle difficulty"],
vec!["/", "Search"],
vec!["c", "Open config file"],
vec!["*", "Sync database!"],
vec!["q", "quit!"],
],
visible: Default::default(),
};
Expand Down
7 changes: 7 additions & 0 deletions leetcode-tui-db/src/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[derive(Default, PartialEq, Eq)]
pub enum Difficulty {
#[default]
Easy,
Medium,
Hard,
}
Loading