From 9d8a8913396ca8456fda563c59af265d0200e4b2 Mon Sep 17 00:00:00 2001 From: "davide.fiocco" Date: Sat, 28 Feb 2026 02:38:56 +0100 Subject: [PATCH 1/2] Simplify command landscape --- README.md | 56 +++---- dev/init.lua | 8 +- doc/code-practice.txt | 103 +++++-------- lua/code-practice/browser.lua | 2 +- lua/code-practice/config.lua | 1 - lua/code-practice/health.lua | 8 +- lua/code-practice/help.lua | 1 + lua/code-practice/init.lua | 12 +- plugin/code-practice.lua | 279 ++++++++++++++-------------------- 9 files changed, 176 insertions(+), 294 deletions(-) diff --git a/README.md b/README.md index fa776d4..759375e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Then populate the exercise database. The simplest way is to import a JSON file (see [`test/example_exercises.json`](test/example_exercises.json) for the expected schema): ```vim -:CPImport /path/to/exercises.json +:CP import /path/to/exercises.json ``` Or set `exercises_json` in your config to auto-import on first run: @@ -55,7 +55,7 @@ export HF_TOKEN=your_token uv run tools/generate_exercises.py tools/syllabus.toml ``` -Or from Neovim: `:CPGenerate`. +Or from Neovim: `:CP generate`. Requirements ------------ @@ -66,31 +66,30 @@ Requirements Quick Start ----------- -1. Open browser: `:CP` (or `cp`) +1. Open browser: `:CP` 2. Navigate with `j`/`k`, open with `Enter` 3. Write your solution in the buffer -4. Run tests: `Ctrl-t` (or `:CPRun`) -5. Move on: `Ctrl-n` (or `:CPNext`) +4. Run tests: `Ctrl-t` +5. Move on: `Ctrl-n` Commands -------- -| Command | Description | -|----------------|--------------------------------------| -| `:CP [action]` | Open/close/refresh browser or stats | -| `:CPRun` | Run tests for the current exercise | -| `:CPNext` | Open the next unsolved exercise | -| `:CPPrev` | Go back to the previous exercise | -| `:CPSkip` | Skip current exercise and move on | -| `:CPDesc` | Show exercise description popup | -| `:CPHint` | Show hints for the current exercise | -| `:CPSolution` | Show reference solution in a split | -| `:CPStats` | Show practice statistics | -| `:CPHelp` | Show the in-editor quick guide | -| `:CPImport` | Import exercises from a JSON file | -| `:CPImport!` | Replace all exercises from JSON | -| `:CPGenerate` | Generate exercises via LLM | - -All commands support tab completion -- type `:CP` to explore. +Everything goes through a single `:CP` command with subcommands. +Tab completion is supported: type `:CP ` to explore. + +| Command | Description | +|-----------------------|--------------------------------------| +| `:CP` or `:CP open` | Open exercise browser | +| `:CP close` | Close the browser | +| `:CP refresh` | Refresh the browser list | +| `:CP stats` | Show practice statistics | +| `:CP help` | Show the in-editor quick guide | +| `:CP import ` | Import exercises from a JSON file | +| `:CP! import ` | Replace all exercises from JSON | +| `:CP generate` | Generate exercises via LLM | + +Exercise-level actions (run tests, next, skip, hints, solution, etc.) are +available via buffer-local keymaps only (see below). Browser Keymaps --------------- @@ -110,13 +109,6 @@ Browser Keymaps | `Esc` | Close browser | | `?` | Show help guide | -Global Keymaps --------------- -| Key | Action | -|----------------|--------------------------| -| `cp` | Open exercise browser | -| `cps` | Show statistics | - Exercise Buffer Keymaps ----------------------- Active in normal mode inside exercise buffers. All use Ctrl shortcuts for @@ -162,17 +154,17 @@ uv run tools/generate_exercises.py tools/syllabus.toml --dry-run uv run tools/generate_exercises.py tools/syllabus.toml --engines my_engines.toml ``` -Or from Neovim: `:CPGenerate` (prompts for topic, count, difficulty, and engine). +Or from Neovim: `:CP generate` (prompts for topic, count, difficulty, and engine). Data ---- Exercises are stored in an SQLite database at `stdpath("data")/code-practice/exercises.db`. -Import exercises from a JSON file with `:CPImport `, or use `:CPImport!` to +Import exercises from a JSON file with `:CP import `, or use `:CP! import ` to replace existing data. The database path is configurable via `storage.db_path`. Roadmap ------- -- [ ] Random exercise (`:CPRandom`) +- [ ] Random exercise (`:CP random`) - [ ] Search widget in browser - [ ] Bug-finding exercise type - [ ] Context-aware LLM hint based on current buffer code diff --git a/dev/init.lua b/dev/init.lua index c7a3dee..f3d1091 100644 --- a/dev/init.lua +++ b/dev/init.lua @@ -45,11 +45,5 @@ require("code-practice").setup({ enabled = true, }, }, - keymaps = { - browser = { - open = "cp", - run = "cpr", - stats = "cps", - }, - }, + keymaps = {}, }) diff --git a/doc/code-practice.txt b/doc/code-practice.txt index 6361831..d7f9c16 100644 --- a/doc/code-practice.txt +++ b/doc/code-practice.txt @@ -59,79 +59,54 @@ Requirements: Run |:checkhealth| |code-practice| to verify all dependencies. -After installing, populate the exercise database with |:CPImport| or -|:CPGenerate|. +After installing, populate the exercise database with |:CP| `import` or +|:CP| `generate`. ============================================================================== 3. QUICK START *code-practice-quickstart* -1. Open the browser |:CP| or `cp` +1. Open the browser |:CP| 2. Navigate with `j`/`k` Press `Enter` to open an exercise 3. Write your solution Edit the buffer -4. Run tests |:CPRun| or `Ctrl-t` -5. Move on |:CPNext| or `Ctrl-n` +4. Run tests `Ctrl-t` +5. Move on `Ctrl-n` ============================================================================== 4. COMMANDS *code-practice-commands* -All commands support tab completion. Type `:CP` to explore. +Everything goes through a single `:CP` command with subcommands. +Tab completion is supported: type `:CP ` to explore. *:CP* -:CP [action] Open, close, or refresh the browser, or show stats. - Actions: `open` (default), `close`, `refresh`, `stats`. +:CP Open the exercise browser (same as `:CP open`). - *:CodePractice* -:CodePractice [action] Long form of |:CP|. +:CP open Open the exercise browser. - *:CPRun* -:CPRun Run tests for the current exercise. For theory - exercises, reads the answer from the `Answer:` line - in the buffer (press 1-4 to select). +:CP close Close the exercise browser. - *:CPNext* -:CPNext Open the next unsolved exercise. Skips exercises - marked with |:CPSkip| in the current session. +:CP refresh Refresh the exercise browser list. - *:CPPrev* -:CPPrev Go back to the previous exercise in the session - history. - - *:CPSkip* -:CPSkip Skip the current exercise and advance to the next - unsolved one. Skipped exercises are remembered for - the current session only. - - *:CPDesc* -:CPDesc Show the exercise description in a floating popup. - - *:CPHint* -:CPHint Show hints for the current exercise (if available). - - *:CPSolution* -:CPSolution Show the reference solution in a vertical split. - - *:CPStats* -:CPStats Show practice statistics (total, solved, by +:CP stats Show practice statistics (total, solved, by difficulty). - *:CPHelp* -:CPHelp Show an in-editor keymap cheat-sheet reflecting your +:CP help Show an in-editor keymap cheat-sheet reflecting your current configuration. For full documentation, see this help file. - *:CPImport* -:CPImport {path} Import exercises from a JSON file. The expected +:CP import {path} Import exercises from a JSON file. The expected schema matches `test/example_exercises.json` in the plugin source. -:CPImport! {path} Replace all existing exercises, test cases, attempts, +:CP import! {path} Replace all existing exercises, test cases, attempts, and theory options with the contents of {path}. - *:CPGenerate* -:CPGenerate Interactively generate exercises via Hugging Face +:CP generate Interactively generate exercises via Hugging Face LLM. Prompts for topic, count, difficulty, and engine. See |code-practice-generate| for setup. +Exercise-level actions (run tests, next, skip, hints, solution, etc.) are +available via buffer-local keymaps only. See |code-practice-keymaps-exercise|. + ============================================================================== 5. KEYMAPS *code-practice-keymaps* @@ -156,7 +131,7 @@ Active inside the browser window opened by |:CP|. `gg` Go to top of list `G` Go to bottom of list `q` / `Esc` Close browser - `?` Show keymap cheat-sheet (|:CPHelp|) + `?` Show keymap cheat-sheet (|:CP| `help`) EXERCISE BUFFER KEYMAPS ~ *code-practice-keymaps-exercise* @@ -166,23 +141,14 @@ use Ctrl shortcuts for single-chord access. Keymaps are configurable via `keymaps.exercise` in config. Key Action ~ - `Ctrl-t` Run tests (|:CPRun|) - `Ctrl-n` Next exercise (|:CPNext|) - `Ctrl-p` Previous exercise (|:CPPrev|) - `Ctrl-k` Skip exercise (|:CPSkip|) - `Ctrl-i` Show hints (|:CPHint|) - `Ctrl-l` View solution (|:CPSolution|) - `Ctrl-d` Show description (|:CPDesc|) - `Ctrl-b` Open browser (|:CP|) - -GLOBAL KEYMAPS ~ - *code-practice-keymaps-global* - -Set during |code-practice.setup()|. - - Key Action ~ - `cp` Open exercise browser - `cps` Show statistics + `Ctrl-t` Run tests + `Ctrl-n` Next exercise + `Ctrl-p` Previous exercise + `Ctrl-k` Skip exercise + `Ctrl-i` Show hints + `Ctrl-l` View solution + `Ctrl-d` Show description + `Ctrl-b` Open browser ============================================================================== 6. CONFIGURATION *code-practice-config* @@ -220,7 +186,6 @@ Options are merged with |vim.tbl_deep_extend()|. keymaps = { browser = { - open = "cp", open_item = "", filter_easy = "e", filter_medium = "m", @@ -290,7 +255,7 @@ Run |:checkhealth| |code-practice| to verify both are available. FROM NEOVIM ~ -Use |:CPGenerate|. It will prompt for topic, count, difficulty, and engine. +Use |:CP| `generate`. It will prompt for topic, count, difficulty, and engine. FROM THE COMMAND LINE ~ @@ -330,11 +295,11 @@ its test cases, attempts, and options. IMPORTING ~ -Use |:CPImport| to load exercises from a JSON file: >vim - :CPImport /path/to/exercises.json +Use |:CP| `import` to load exercises from a JSON file: >vim + :CP import /path/to/exercises.json < Use the bang form to wipe all existing data and replace: >vim - :CPImport! /path/to/exercises.json + :CP! import /path/to/exercises.json < Set `storage.exercises_json` in your config to auto-import on first run when the database is empty. @@ -350,8 +315,8 @@ Run `:checkhealth code-practice` to verify: - Neovim version (0.10+ required) - nui.nvim and sqlite.lua plugins - Engine executables based on enabled config (iterates the registry) -- uv executable (needed for |:CPGenerate|) -- HF_TOKEN environment variable (needed for |:CPGenerate|) +- uv executable (needed for |:CP| `generate`) +- HF_TOKEN environment variable (needed for |:CP| `generate`) - Database file existence ============================================================================== diff --git a/lua/code-practice/browser.lua b/lua/code-practice/browser.lua index 5266b76..5314797 100644 --- a/lua/code-practice/browser.lua +++ b/lua/code-practice/browser.lua @@ -150,7 +150,7 @@ function browser.render_preview() end table.insert(lines, "") - table.insert(lines, "Press Enter to open, then :CPRun to test") + table.insert(lines, "Press Enter to open, then to run tests") state.preview_cache[exercise.id] = lines return lines diff --git a/lua/code-practice/config.lua b/lua/code-practice/config.lua index b032af9..8b22ca6 100644 --- a/lua/code-practice/config.lua +++ b/lua/code-practice/config.lua @@ -37,7 +37,6 @@ M.defaults = { keymaps = { browser = { - open = "cp", open_item = "", filter_easy = "e", filter_medium = "m", diff --git a/lua/code-practice/health.lua b/lua/code-practice/health.lua index 0991282..6a78278 100644 --- a/lua/code-practice/health.lua +++ b/lua/code-practice/health.lua @@ -42,11 +42,11 @@ function M.check() end end - -- :CPGenerate dependencies + -- :CP generate dependencies if vim.fn.executable("uv") == 1 then - vim.health.ok("uv found (needed for :CPGenerate)") + vim.health.ok("uv found (needed for :CP generate)") else - vim.health.warn("uv not found", { "Install uv (https://github.com/astral-sh/uv) to use :CPGenerate" }) + vim.health.warn("uv not found", { "Install uv (https://github.com/astral-sh/uv) to use :CP generate" }) end if vim.env.HF_TOKEN and vim.env.HF_TOKEN ~= "" then @@ -54,7 +54,7 @@ function M.check() else vim.health.warn( "HF_TOKEN not set", - { "Set the HF_TOKEN environment variable to use :CPGenerate with Hugging Face models" } + { "Set the HF_TOKEN environment variable to use :CP generate with Hugging Face models" } ) end diff --git a/lua/code-practice/help.lua b/lua/code-practice/help.lua index 58cdc64..852def7 100644 --- a/lua/code-practice/help.lua +++ b/lua/code-practice/help.lua @@ -112,6 +112,7 @@ function help.show() 17 ) .. "Open browser", "", + " Commands: :CP open | stats | help | import | generate", " See :help code-practice for full documentation", "", " Press q, , or to close", diff --git a/lua/code-practice/init.lua b/lua/code-practice/init.lua index 6c3d182..2ef9d44 100644 --- a/lua/code-practice/init.lua +++ b/lua/code-practice/init.lua @@ -57,7 +57,7 @@ function code_practice.setup(opts) utils.notify("Auto-import failed: " .. (err or "unknown error"), "error") end else - utils.notify("No exercises found. Run :CPImport to load exercises from a JSON file.", "warn") + utils.notify("No exercises found. Run :CP import to load exercises from a JSON file.", "warn") end end @@ -65,16 +65,6 @@ function code_practice.setup(opts) code_practice.open_exercise(id) end) - local keymaps = config.get("keymaps.browser") - - vim.keymap.set("n", keymaps.open or "cp", function() - code_practice.open_browser() - end, { desc = "Open Code Practice browser" }) - - vim.keymap.set("n", keymaps.stats or "cps", function() - vim.cmd("CPStats") - end, { desc = "Show statistics" }) - utils.delete_temp_files() utils.notify("Code Practice initialized!") diff --git a/plugin/code-practice.lua b/plugin/code-practice.lua index 7137b38..706e1ca 100644 --- a/plugin/code-practice.lua +++ b/plugin/code-practice.lua @@ -1,185 +1,126 @@ -- Code Practice - Plugin Commands local code_practice = require("code-practice.init") -vim.api.nvim_create_user_command("CodePractice", function(opts) - local args = opts.fargs[1] or "open" +vim.api.nvim_create_user_command("CP", function(opts) + local args = opts.fargs + local sub = args[1] or "open" - if args == "open" or args == "" then + if sub == "open" or sub == "" then code_practice.open_browser() - elseif args == "close" then + elseif sub == "close" then code_practice.close_browser() - elseif args == "refresh" then + elseif sub == "refresh" then code_practice.refresh_browser() - elseif args == "stats" then + elseif sub == "stats" then code_practice.show_stats() - else - vim.notify("Unknown command: " .. args, vim.log.levels.WARN) - end -end, { - nargs = "?", - complete = function() - return { "open", "close", "refresh", "stats" } - end, -}) - -vim.api.nvim_create_user_command("CP", function(opts) - vim.cmd("CodePractice " .. (opts.fargs[1] or "open")) -end, { - nargs = "?", - complete = function() - return { "open", "close", "refresh", "stats" } - end, -}) - -vim.api.nvim_create_user_command("CPRun", function() - code_practice.run_tests() -end, { - desc = "Run tests for current exercise", -}) - -vim.api.nvim_create_user_command("CPNext", function() - code_practice.next_exercise() -end, { - desc = "Open next exercise", -}) - -vim.api.nvim_create_user_command("CPSkip", function() - code_practice.skip_exercise() -end, { - desc = "Skip current exercise", -}) - -vim.api.nvim_create_user_command("CPPrev", function() - code_practice.prev_exercise() -end, { - desc = "Open previous exercise in session", -}) - -vim.api.nvim_create_user_command("CPStats", function() - code_practice.show_stats() -end, { - desc = "Show practice statistics", -}) - -vim.api.nvim_create_user_command("CPHint", function() - code_practice.show_hints() -end, { - desc = "Show hints for current exercise", -}) - -vim.api.nvim_create_user_command("CPSolution", function() - code_practice.show_solution() -end, { - desc = "Show solution for current exercise", -}) + elseif sub == "help" then + code_practice.show_help() + elseif sub == "import" then + local path = args[2] or require("code-practice.config").get("storage.exercises_json") or "" + if path == "" then + vim.notify("[code-practice] Usage: :CP import ", vim.log.levels.WARN) + return + end + local importer = require("code-practice.importer") + local counts, err = importer.import(path, { replace = opts.bang }) + if counts then + local mode = opts.bang and "Replaced with" or "Imported" + vim.notify( + string.format( + "[code-practice] %s %d exercises, %d test cases, %d theory options", + mode, + counts.exercises, + counts.test_cases, + counts.theory_options + ), + vim.log.levels.INFO + ) + local browser = require("code-practice.browser") + if browser.refresh then + browser.refresh() + end + else + vim.notify("[code-practice] Import failed: " .. (err or "unknown"), vim.log.levels.ERROR) + end + elseif sub == "generate" then + local topic = vim.fn.input("Topic: ") + if not topic or topic == "" then + return + end + local count = vim.fn.input("Count [5]: ") + count = (count and count ~= "") and count or "5" + local difficulty = vim.fn.input("Difficulty (easy/medium/hard) [medium]: ") + difficulty = (difficulty and difficulty ~= "") and difficulty or "medium" + local engine_names = table.concat(require("code-practice.engines").list(), "/") + local engine = vim.fn.input("Engine (" .. engine_names .. ") [python]: ") + engine = (engine and engine ~= "") and engine or "python" + + local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h:h") + local script = plugin_dir .. "/tools/generate_exercises.py" + local db_path = require("code-practice.config").get("storage.db_path") + + local tmp = vim.fn.tempname() .. ".toml" + local toml = string.format( + '[[exercises]]\ntopic = "%s"\nengine = "%s"\ndifficulty = "%s"\ncount = %s\n', + topic:gsub('"', '\\"'), + engine, + difficulty, + count + ) + vim.fn.writefile(vim.split(toml, "\n"), tmp) -vim.api.nvim_create_user_command("CPDesc", function() - code_practice.show_description() -end, { - desc = "Show description for current exercise", -}) + local cmd = { "uv", "run", script, tmp, "--db-path", db_path } -vim.api.nvim_create_user_command("CPHelp", function() - code_practice.show_help() -end, { - desc = "Show Code Practice quick guide", -}) + vim.notify("[code-practice] Generating exercises...", vim.log.levels.INFO) -vim.api.nvim_create_user_command("CPImport", function(opts) - local path = opts.fargs[1] or require("code-practice.config").get("storage.exercises_json") or "" - if path == "" then - vim.notify("[code-practice] Usage: :CPImport ", vim.log.levels.WARN) - return - end - local importer = require("code-practice.importer") - local counts, err = importer.import(path, { replace = opts.bang }) - if counts then - local mode = opts.bang and "Replaced with" or "Imported" - vim.notify( - string.format( - "[code-practice] %s %d exercises, %d test cases, %d theory options", - mode, - counts.exercises, - counts.test_cases, - counts.theory_options - ), - vim.log.levels.INFO - ) - local browser = require("code-practice.browser") - if browser.refresh then - browser.refresh() - end + local output_lines = {} + vim.fn.jobstart(cmd, { + stdout_buffered = true, + stderr_buffered = true, + on_stdout = function(_, data) + if data then + vim.list_extend(output_lines, data) + end + end, + on_stderr = function(_, data) + if data then + vim.list_extend(output_lines, data) + end + end, + on_exit = function(_, exit_code) + vim.fn.delete(tmp) + vim.schedule(function() + local msg = table.concat(output_lines, "\n") + if exit_code == 0 then + vim.notify("[code-practice] " .. msg, vim.log.levels.INFO) + local browser = require("code-practice.browser") + if browser.refresh then + browser.refresh() + end + else + vim.notify("[code-practice] Generation failed:\n" .. msg, vim.log.levels.ERROR) + end + end) + end, + }) else - vim.notify("[code-practice] Import failed: " .. (err or "unknown"), vim.log.levels.ERROR) + vim.notify("[code-practice] Unknown subcommand: " .. sub, vim.log.levels.WARN) end end, { - nargs = "?", + nargs = "*", bang = true, - complete = "file", - desc = "Import exercises from JSON (use ! to replace existing)", -}) - -vim.api.nvim_create_user_command("CPGenerate", function() - local topic = vim.fn.input("Topic: ") - if not topic or topic == "" then - return - end - local count = vim.fn.input("Count [5]: ") - count = (count and count ~= "") and count or "5" - local difficulty = vim.fn.input("Difficulty (easy/medium/hard) [medium]: ") - difficulty = (difficulty and difficulty ~= "") and difficulty or "medium" - local engine_names = table.concat(require("code-practice.engines").list(), "/") - local engine = vim.fn.input("Engine (" .. engine_names .. ") [python]: ") - engine = (engine and engine ~= "") and engine or "python" - - local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h:h") - local script = plugin_dir .. "/tools/generate_exercises.py" - local db_path = require("code-practice.config").get("storage.db_path") - - local tmp = vim.fn.tempname() .. ".toml" - local toml = string.format( - '[[exercises]]\ntopic = "%s"\nengine = "%s"\ndifficulty = "%s"\ncount = %s\n', - topic:gsub('"', '\\"'), - engine, - difficulty, - count - ) - vim.fn.writefile(vim.split(toml, "\n"), tmp) - - local cmd = { "uv", "run", script, tmp, "--db-path", db_path } - - vim.notify("[code-practice] Generating exercises...", vim.log.levels.INFO) - - local output_lines = {} - vim.fn.jobstart(cmd, { - stdout_buffered = true, - stderr_buffered = true, - on_stdout = function(_, data) - if data then - vim.list_extend(output_lines, data) - end - end, - on_stderr = function(_, data) - if data then - vim.list_extend(output_lines, data) - end - end, - on_exit = function(_, exit_code) - vim.fn.delete(tmp) - vim.schedule(function() - local msg = table.concat(output_lines, "\n") - if exit_code == 0 then - vim.notify("[code-practice] " .. msg, vim.log.levels.INFO) - local browser = require("code-practice.browser") - if browser.refresh then - browser.refresh() - end - else - vim.notify("[code-practice] Generation failed:\n" .. msg, vim.log.levels.ERROR) - end - end) - end, - }) -end, { - desc = "Generate exercises via Hugging Face LLM", + complete = function(arg_lead, cmd_line) + local parts = vim.split(cmd_line, "%s+") + if #parts <= 2 then + local subs = { "open", "close", "refresh", "stats", "help", "import", "generate" } + return vim.tbl_filter(function(s) + return s:find(arg_lead, 1, true) == 1 + end, subs) + end + if parts[2] == "import" then + return vim.fn.getcompletion(arg_lead, "file") + end + return {} + end, + desc = "Code Practice commands", }) From 0addb212e1bd63fa8a407b2cc1ceb6377ca8699a Mon Sep 17 00:00:00 2001 From: "davide.fiocco" Date: Sat, 28 Feb 2026 02:49:12 +0100 Subject: [PATCH 2/2] Implement problem numbering --- doc/code-practice.txt | 2 +- lua/code-practice/browser.lua | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/code-practice.txt b/doc/code-practice.txt index d7f9c16..9375ffe 100644 --- a/doc/code-practice.txt +++ b/doc/code-practice.txt @@ -168,7 +168,7 @@ Options are merged with |vim.tbl_deep_extend()|. width = 0.8, -- fraction of editor width height = 0.8, -- fraction of editor height border = "rounded", - show_numbers = true, + show_numbers = true, -- show exercise index in browser list }, -- Engine defaults are built from the registry (see |code-practice-engines|). diff --git a/lua/code-practice/browser.lua b/lua/code-practice/browser.lua index 5314797..4a3cfaf 100644 --- a/lua/code-practice/browser.lua +++ b/lua/code-practice/browser.lua @@ -62,7 +62,11 @@ function browser.render_exercise_list() local engine_icon = engines.icon(ex.engine) local solved_icon = state.solved_ids[ex.id] and "✓ " or " " - local line = string.format("%s%s %s %s%s", prefix, diff_icon, engine_icon, solved_icon, ex.title) + local num_str = "" + if config.get("ui.show_numbers") then + num_str = string.format("%d. ", i) + end + local line = string.format("%s%s %s %s%s%s", prefix, diff_icon, engine_icon, solved_icon, num_str, ex.title) table.insert(lines, line) end