diff options
| author | ruki <[email protected]> | 2025-10-27 14:17:54 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-10-27 14:17:54 +0800 |
| commit | 06dd715e91d3fed3b8101801eceda228c3ed4877 (patch) | |
| tree | 8c370783dd532b6cd8bec516af2644167ce22a4a /tests/modules | |
| parent | 954944d14d86d2ca4747887150a35b440864c03d (diff) | |
| parent | 988bc41fb22cdd7e4e005fc985576d46b2fe20d9 (diff) | |
Merge pull request #6970 from xmake-io/tty
Improve tty
Diffstat (limited to 'tests/modules')
| -rw-r--r-- | tests/modules/tty/cursor_control.lua | 133 | ||||
| -rw-r--r-- | tests/modules/tty/live_dashboard.lua | 162 | ||||
| -rw-r--r-- | tests/modules/tty/quick_example.lua | 137 | ||||
| -rw-r--r-- | tests/modules/tty/test.lua | 213 |
4 files changed, 645 insertions, 0 deletions
diff --git a/tests/modules/tty/cursor_control.lua b/tests/modules/tty/cursor_control.lua new file mode 100644 index 000000000..4502617eb --- /dev/null +++ b/tests/modules/tty/cursor_control.lua @@ -0,0 +1,133 @@ +import("core.base.tty") + +-- Demo TTY cursor position control features +-- Implement partial screen refresh + +function _progress_bar_inline(label, progress) + local bar_width = 40 + local filled = math.floor(progress * bar_width) + local bar = string.rep("█", filled) .. string.rep("░", bar_width - filled) + + -- Clear the line and output content + tty.cr() + tty.erase_line() + io.write(string.format("%s: [%s] %3d%%", label, bar, math.floor(progress * 100))) + io.flush() +end + +function main() + -- Check if ANSI control codes are supported + if not tty.has_vtansi() then + print("Terminal does not support ANSI control codes") + return + end + + print("=== TTY Cursor Control Demo ===") + print("This demo shows partial screen refresh using cursor positioning") + print("") + + -- Hide cursor for smoother display + tty.cursor_hide() + + print("\nDemo 1: Multiple Progress Bars (Parallel Updates)") + print("------------------------------------------------") + + -- Reserve lines for progress bars + io.write("Task 1: Downloading\n") + io.write("Task 2: Compiling \n") + io.write("Task 3: Linking \n") + io.flush() + + -- Simulate three parallel task progress bars + for step = 1, 100 do + -- Move to Task 1 line (3 lines up from current position) + tty.cursor_move_up(3) + + -- Update Task 1 progress + local progress1 = step / 100 + _progress_bar_inline("Task 1: Downloading", progress1) + io.write("\n") + + -- Update Task 2 progress (starts later, progresses faster) + local progress2 = math.max(0, math.min(1.0, (step - 10) / 80)) + _progress_bar_inline("Task 2: Compiling ", progress2) + io.write("\n") + + -- Update Task 3 progress (starts even later) + local progress3 = math.max(0, math.min(1.0, (step - 30) / 60)) + _progress_bar_inline("Task 3: Linking ", progress3) + io.write("\n") + io.flush() + + os.sleep(30) -- 30ms delay + end + + print("\n\nDemo 2: Dynamic Status Updates") + print("--------------------------------") + + -- Print initial status and counter + io.write("Status: Waiting...\n") + io.write("Counter: 0\n") + io.flush() + + local statuses = { + "Initializing...", + "Loading configuration...", + "Parsing files...", + "Building dependencies...", + "Compiling sources...", + "Linking executable...", + "Done!" + } + + for i, status in ipairs(statuses) do + -- Move to status line (2 lines up) + tty.cursor_move_up(2) + + -- Update status line + tty.cr() + tty.erase_line() + io.write(string.format("Status: %s", status)) + io.write("\n") + + -- Update counter line + tty.cr() + tty.erase_line() + io.write(string.format("Counter: %d", i * 100)) + io.write("\n") + io.flush() + + os.sleep(500) + end + + -- Restore cursor visibility + tty.cursor_show() + + print("\n\nDemo 3: Erase and Update Specific Line") + print("----------------------------------------") + + io.write("Line 1: This line will stay\n") + io.write("Line 2: This line will be updated...\n") + io.write("Line 3: This line will stay too\n") + io.flush() + + os.sleep(1000) + + -- Update only the middle line (2 lines up) + tty.cursor_move_up(2) + tty.cr() + tty.erase_line() + io.write("Line 2: Updated content! ✓\n") + io.flush() + + -- Move cursor to end + tty.cursor_move_down(1) + print("\n\nAll demos completed!") + print("\nKey features demonstrated:") + print(" - cursor_move_up/down/left/right: Move cursor relatively") + print(" - cursor_save/restore: Save and restore cursor position") + print(" - erase_line(): Clear current line") + print(" - cursor_hide()/cursor_show(): Control cursor visibility") + print(" - Partial screen refresh without clearing entire screen") +end + diff --git a/tests/modules/tty/live_dashboard.lua b/tests/modules/tty/live_dashboard.lua new file mode 100644 index 000000000..c479d487c --- /dev/null +++ b/tests/modules/tty/live_dashboard.lua @@ -0,0 +1,162 @@ +import("core.base.tty") +import("core.base.scheduler") + +-- Demo advanced usage: Live updating dashboard +-- Similar to build tool real-time output + +function simulate_build_process() + if not tty.has_vtansi() then + print("Terminal does not support ANSI control codes") + return + end + + print("\nBuild Dashboard - Live Updates") + print(string.rep("=", 60)) + + -- Hide cursor + tty.cursor_hide() + + -- Print task list + io.write("⏸ Parse project files [" .. string.rep("░", 30) .. "] 0%\n") + io.write("⏸ Resolve dependencies [" .. string.rep("░", 30) .. "] 0%\n") + io.write("⏸ Compile sources [" .. string.rep("░", 30) .. "] 0%\n") + io.write("⏸ Link executable [" .. string.rep("░", 30) .. "] 0%\n") + io.write("⏸ Create package [" .. string.rep("░", 30) .. "] 0%\n") + io.write("\nRecent logs:\n") + io.flush() + + local log_count = 0 + + local function update_task_line(task_index, icon, name, progress) + -- Move to the task line (from current cursor position) + -- We need to go up: log_count lines + 1 separator line + (5 - task_index) task lines + local lines_up = log_count + 1 + (5 - task_index + 1) + tty.cursor_move_up(lines_up) + + tty.cr() + tty.erase_line() + local bar_width = 30 + local filled = math.floor(progress * bar_width) + local bar = string.rep("█", filled) .. string.rep("░", bar_width - filled) + io.write(string.format("%s %-24s [%s] %3d%%\n", icon, name, bar, math.floor(progress * 100))) + + -- Move back down + tty.cursor_move_down(lines_up - 1) + io.flush() + end + + local function add_log(message) + io.write(string.format("[%s] %s\n", os.date("%H:%M:%S"), message)) + io.flush() + log_count = log_count + 1 + end + + scheduler.co_start(function() + -- Task 1: Parse (index 1, top task) + add_log("Starting project parsing...") + for i = 1, 20 do + update_task_line(1, "▶", "Parse project files", i / 20) + os.sleep(50) + end + update_task_line(1, "✓", "Parse project files", 1.0) + add_log("Project parsed successfully") + os.sleep(200) + + -- Task 2: Dependencies (index 2) + add_log("Resolving dependencies...") + for i = 1, 15 do + update_task_line(2, "▶", "Resolve dependencies", i / 15) + os.sleep(80) + end + update_task_line(2, "✓", "Resolve dependencies", 1.0) + add_log("Dependencies resolved: 12 packages") + os.sleep(200) + + -- Task 3: Compile (index 3) + add_log("Compiling source files...") + local source_files = {"main.cpp", "utils.cpp", "config.cpp", "parser.cpp", "builder.cpp"} + for i = 1, #source_files do + update_task_line(3, "▶", "Compile sources", i / #source_files) + add_log("Compiling " .. source_files[i]) + os.sleep(300) + end + update_task_line(3, "✓", "Compile sources", 1.0) + add_log("Compilation completed: 5 files") + os.sleep(200) + + -- Task 4: Link (index 4) + add_log("Linking executable...") + for i = 1, 10 do + update_task_line(4, "▶", "Link executable", i / 10) + os.sleep(100) + end + update_task_line(4, "✓", "Link executable", 1.0) + add_log("Executable created: build/myapp") + os.sleep(200) + + -- Task 5: Package (index 5, bottom task) + add_log("Creating package...") + for i = 1, 8 do + update_task_line(5, "▶", "Create package", i / 8) + os.sleep(120) + end + update_task_line(5, "✓", "Create package", 1.0) + add_log("Package created: dist/myapp-1.0.0.tar.gz") + + -- Done + os.sleep(500) + add_log("Build completed successfully! 🎉") + + -- Restore cursor + os.sleep(1000) + tty.cursor_show() + + print("\nBuild process completed!") + end) +end + +function demo_spinner() + if not tty.has_vtansi() then + return + end + + print("\n\n=== Spinner Demo ===") + io.write("Loading\n") + io.flush() + + local frames = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} + tty.cursor_hide() + + for round = 1, 3 do + for _, frame in ipairs(frames) do + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write(string.format("Loading %s [round %d/3]\n", frame, round)) + io.flush() + os.sleep(80) + end + end + + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Loading ✓ Complete!\n") + io.flush() + tty.cursor_show() +end + +function main() + print("\n" .. string.rep("=", 60)) + print("TTY Live Dashboard Demo") + print(string.rep("=", 60)) + + -- Demo 1: Spinner + demo_spinner() + + os.sleep(1000) + + -- Demo 2: Build Dashboard + simulate_build_process() +end + diff --git a/tests/modules/tty/quick_example.lua b/tests/modules/tty/quick_example.lua new file mode 100644 index 000000000..794a79d58 --- /dev/null +++ b/tests/modules/tty/quick_example.lua @@ -0,0 +1,137 @@ +-- Quick example: Demonstrates the most common use cases + +import("core.base.tty") + +function example1_simple_progress() + print("\n=== Example 1: Simple Progress Bar ===\n") + + if not tty.has_vtansi() then + print("ANSI not supported") + return + end + + print("Downloading file...") + for i = 0, 100, 5 do + local width = 40 + local filled = math.floor(i / 100 * width) + local bar = string.rep("█", filled) .. string.rep("░", width - filled) + + -- Move to start of line, clear it, and redraw + tty.cr() + tty.erase_line() + io.write(string.format("[%s] %3d%%", bar, i)) + io.flush() + + os.sleep(50) + end + print("") -- New line after progress +end + +function example2_update_previous_line() + print("\n=== Example 2: Update Previous Line ===\n") + + if not tty.has_vtansi() then + print("ANSI not supported") + return + end + + io.write("Building project...\n") + io.write("Status: Starting...\n") + io.flush() + + os.sleep(1000) + + -- Go back and update the status line + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Status: Compiling files...\n") + io.flush() + + os.sleep(1000) + + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Status: Done! ✓\n") + io.flush() +end + +function example3_multi_line_update() + print("\n=== Example 3: Multi-line Updates ===\n") + + if not tty.has_vtansi() then + print("ANSI not supported") + return + end + + -- Create a simple status board + io.write("Task 1: Waiting...\n") + io.write("Task 2: Waiting...\n") + io.write("Task 3: Waiting...\n") + io.flush() + + tty.cursor_hide() + + -- Update Task 1 to Running + tty.cursor_move_up(3) + tty.cr() + tty.erase_line() + io.write("Task 1: Running... \n") + io.flush() + os.sleep(500) + + -- Update Task 1 to Done + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Task 1: Done ✓\n") + io.flush() + + -- Update Task 2 to Running + tty.cr() + tty.erase_line() + io.write("Task 2: Running... \n") + io.flush() + os.sleep(500) + + -- Update Task 2 to Done + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Task 2: Done ✓\n") + io.flush() + + -- Update Task 3 to Running + tty.cr() + tty.erase_line() + io.write("Task 3: Running... \n") + io.flush() + os.sleep(500) + + -- Update Task 3 to Done + tty.cursor_move_up(1) + tty.cr() + tty.erase_line() + io.write("Task 3: Done ✓\n") + io.flush() + + tty.cursor_show() +end + +function main() + print(string.rep("=", 60)) + print("TTY Cursor Control - Quick Examples") + print(string.rep("=", 60)) + + example1_simple_progress() + example2_update_previous_line() + example3_multi_line_update() + + print("\n" .. string.rep("=", 60)) + print("All examples completed!") + print("Check test.lua, cursor_control.lua, and live_dashboard.lua") + print("for more advanced examples.") + print(string.rep("=", 60)) +end + diff --git a/tests/modules/tty/test.lua b/tests/modules/tty/test.lua new file mode 100644 index 000000000..f236fcbeb --- /dev/null +++ b/tests/modules/tty/test.lua @@ -0,0 +1,213 @@ +import("core.base.tty") + +function test_cursor_move() + if not tty.has_vtansi() then + print("Terminal does not support ANSI control codes, skipping test") + return + end + + print("\n=== Test: cursor_move ===") + io.write("Line 1\n") + io.write("Line 2\n") + io.write("Line 3\n") + io.write("Line 4\n") + io.write("Line 5\n") + io.flush() + + os.sleep(1000) + + -- Move to line 3 and update content + tty.cursor_move_up(3) + tty.cr() + tty.erase_line() + io.write("Line 3 - UPDATED!\n") + io.flush() + + -- Move cursor back to end + tty.cursor_move_down(2) + + print("\n✓ cursor_move_up test passed") +end + +function test_cursor_move_directions() + if not tty.has_vtansi() then + return + end + + print("\n=== Test: cursor movement directions ===") + + -- Create a small coordinate system + for i = 1, 5 do + io.write(string.rep(" ", 60) .. "\n") + end + io.flush() + + -- Move to starting position + tty.cursor_move_up(5) + tty.cursor_move_right(10) + io.write("START") + io.flush() + os.sleep(500) + + -- Move right + tty.cursor_move_right(10) + io.write("RIGHT") + io.flush() + os.sleep(500) + + -- Move down + tty.cursor_move_down(2) + io.write("DOWN") + io.flush() + os.sleep(500) + + -- Move left + tty.cursor_move_left(5) + io.write("LEFT") + io.flush() + os.sleep(500) + + -- Move up + tty.cursor_move_up(1) + io.write("UP") + io.flush() + + -- Move to end + tty.cursor_move_down(3) + io.write("\n") + io.flush() + + print("✓ cursor movement directions test passed") +end + +function test_cursor_hide_show() + if not tty.has_vtansi() then + return + end + + print("\n=== Test: cursor hide/show ===") + + print("Cursor is visible now...") + os.sleep(1000) + + tty.cursor_hide() + print("Cursor is hidden now (you shouldn't see it blinking)...") + os.sleep(2000) + + tty.cursor_show() + print("Cursor is visible again!") + + print("✓ cursor hide/show test passed") +end + +function test_erase_operations() + if not tty.has_vtansi() then + return + end + + print("\n=== Test: erase operations ===") + + -- Test erase_line_to_end + io.write("This is a long line that will be partially erased") + io.flush() + os.sleep(1000) + tty.cursor_move_left(30) + tty.erase_line_to_end() + io.write("<- erased to end\n") + io.flush() + + os.sleep(1000) + + -- Test full line erase + io.write("This entire line will be erased") + io.flush() + os.sleep(1000) + tty.cr() + tty.erase_line() + io.write("Replaced!\n") + io.flush() + + print("✓ erase operations test passed") +end + +function test_partial_screen_update() + if not tty.has_vtansi() then + return + end + + print("\n=== Test: partial screen update ===") + + -- Create a table + io.write("┌────────────────────────────────┐\n") + io.write("│ Task │ Status │\n") + io.write("├────────────────────────────────┤\n") + io.write("│ Compile │ Pending... │\n") + io.write("│ Link │ Pending... │\n") + io.write("│ Package │ Pending... │\n") + io.write("└────────────────────────────────┘\n") + io.flush() + + os.sleep(1000) + tty.cursor_hide() + + -- Update first task status + tty.cursor_move_up(4) + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Running... │\n") + io.flush() + os.sleep(800) + + tty.cursor_move_up(1) + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Done! ✓ │\n") + io.flush() + os.sleep(500) + + -- Update second task status + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Running... │\n") + io.flush() + os.sleep(800) + + tty.cursor_move_up(1) + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Done! ✓ │\n") + io.flush() + os.sleep(500) + + -- Update third task status + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Running... │\n") + io.flush() + os.sleep(800) + + tty.cursor_move_up(1) + tty.cursor_move_to_col(32) + tty.erase_line_to_end() + io.write("│ Done! ✓ │\n") + io.flush() + + tty.cursor_show() + tty.cursor_move_down(1) + + print("\n✓ partial screen update test passed") +end + +function main(...) + -- Run all tests + test_cursor_move() + test_cursor_move_directions() + test_cursor_hide_show() + test_erase_operations() + test_partial_screen_update() + + print("\n" .. string.rep("=", 50)) + print("All TTY cursor control tests completed!") + print(string.rep("=", 50)) +end + |
