2 changed files with 240 additions and 1 deletions
@ -0,0 +1,238 @@
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env tclsh |
||||
|
||||
# Copyright (C) 2021, Maxim Lihachev, <envrm@yandex.ru> |
||||
# |
||||
# This program is free software: you can redistribute it and/or modify it |
||||
# under the terms of the GNU General Public License as published by the Free |
||||
# Software Foundation, version 3. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, but WITHOUT |
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
||||
# more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License along with |
||||
# this program. If not, see <https://www.gnu.org/licenses/>. |
||||
|
||||
# |
||||
# Simple Bingo-style TODO visualizer. |
||||
# |
||||
# Input: |
||||
# |
||||
# todo.md: |
||||
# |
||||
# - [ ] A |
||||
# - [x] B |
||||
# - [x] C |
||||
# - [ ] … |
||||
# - [ ] V |
||||
# |
||||
# GUI: |
||||
# |
||||
# ┌───────────────────────┐ |
||||
# ├───┬───┬───┬───┬───┬───┤ |
||||
# │ A │ B │ C │ D │ E │ F │ |
||||
# ├───┼───┼───┼───┼───┼───┤ |
||||
# │ A │ B │ C │ D │ E │ F │ |
||||
# ├───┼───┼───┼───┼───┼───┤ |
||||
# │ G │ H │ I │ J │ K │ L │ |
||||
# ├───┼───┼───┼───┼───┼───┤ |
||||
# │ M │ N │ O │ P │ Q │ R │ |
||||
# ├───┴─┬─┴───┼───┴─┬─┴───┤ |
||||
# │ S │ T │ U │ V │ |
||||
# ├─────┴─────┴─────┴─────┤ |
||||
# │ 10/22 [45%] │ |
||||
# └───────────────────────┘ |
||||
# |
||||
|
||||
# |
||||
# TODO: работа с повторяющимися строками |
||||
# |
||||
|
||||
package require fileutil |
||||
|
||||
proc help {} { |
||||
puts "USAGE: [file tail $::argv0] <todo.md>" |
||||
} |
||||
|
||||
namespace eval todo { |
||||
set file {} |
||||
set data {} |
||||
set list {} |
||||
|
||||
set status {} |
||||
|
||||
array set format { |
||||
marker {^\s*-\s+\[[ xX]\]\s*} |
||||
done {^\s*-\s+\[[xX]\]\s*} |
||||
todo {^\s*-\s+\[[ ]\]\s*} |
||||
} |
||||
|
||||
proc sanitize_string {str} { |
||||
string map { |
||||
{*} {\*} {+} {\+} {[} {\[} {]} {\]} {.} {\.} |
||||
{(} {\(} {)} {\)} {\{} {\\{} {\}} {\\}} |
||||
} $str |
||||
|
||||
} |
||||
|
||||
proc load {filename} { |
||||
todo::update [split [fileutil::cat [set ::todo::file $filename]] "\n"] |
||||
todo::update_status |
||||
} |
||||
|
||||
proc update {data} { |
||||
set ::todo::data $data |
||||
} |
||||
|
||||
proc update_list {} { |
||||
set ::todo::list [lsearch -all -inline -regexp $::todo::data {^[\s]*-[\s]\[[ xX]\]\s.*$}] |
||||
} |
||||
|
||||
proc update_status {} { |
||||
::todo::update_list |
||||
|
||||
set total_items [llength $::todo::list] |
||||
set done_items [llength [regexp -inline -all -nocase {\[[xX]\]} $::todo::list]] |
||||
|
||||
set percents [expr {$done_items * 100 / $total_items}] |
||||
|
||||
set ::todo::status "$done_items/$total_items \[${percents}%\]" |
||||
} |
||||
|
||||
proc update_file {} { |
||||
fileutil::writeFile $::todo::file [join $::todo::data "\n"] |
||||
} |
||||
|
||||
proc update_item {item} { |
||||
set item_pattern $::todo::format(marker)[::todo::sanitize_string [lindex $item end]] |
||||
set todo_string [lsearch -inline -regexp $::todo::data $item_pattern] |
||||
set todo_index [lsearch -exact $::todo::data $todo_string] |
||||
set updated_todo [::todo::toggle_marker $todo_string] |
||||
|
||||
::todo::update_list |
||||
::todo::update [lreplace $::todo::data $todo_index $todo_index $updated_todo] |
||||
::todo::update_status |
||||
::todo::update_file |
||||
} |
||||
|
||||
# Toggle todo markers: |
||||
# "- [ ]" -> "- [x]" |
||||
# "- [x]" -> "- [ ]" |
||||
proc toggle_marker {str} { |
||||
string map {{- [ ]} {- [x]} {- [x]} {- [ ]} {- [X]} {- [ ]}} $str |
||||
} |
||||
} |
||||
|
||||
namespace eval gui { |
||||
proc set_theme {} { |
||||
ttk::style theme use clam |
||||
|
||||
set layout [ttk::style layout TButton] |
||||
set tail [lindex $layout end] |
||||
|
||||
ttk::style layout Button.TODO [lreplace $layout end end $tail] |
||||
ttk::style configure Button.TODO {*}[ttk::style configure TButton] -background LightBlue -wraplength 150 |
||||
ttk::style map Button.TODO {*}[ttk::style map TButton] -background {active LightSteelBlue} |
||||
|
||||
ttk::style layout Button.DONE [lreplace $layout end end $tail] |
||||
ttk::style configure Button.DONE {*}[ttk::style configure TButton] -background GreenYellow -wraplength 150 |
||||
ttk::style map Button.DONE {*}[ttk::style map TButton] -background {active LawnGreen} |
||||
} |
||||
|
||||
proc set_button_status {button status} { |
||||
set status [string toupper $status] |
||||
|
||||
$button configure -style Button.$status |
||||
|
||||
if {$status == "TODO"} { |
||||
set ::gui::state($button) 0 |
||||
} else { |
||||
set ::gui::state($button) 1 |
||||
} |
||||
} |
||||
|
||||
proc toggle_button_status {button} { |
||||
if {$::gui::state($button)} { |
||||
::gui::set_button_status $button todo |
||||
} else { |
||||
::gui::set_button_status $button done |
||||
} |
||||
|
||||
::todo::update_item [lsearch -inline [$button configure] {-text*}] |
||||
} |
||||
|
||||
proc show {} { |
||||
::gui::set_theme |
||||
|
||||
set columns [expr {ceil(sqrt([llength $::todo::list]))}] |
||||
|
||||
lassign {0 0 0} current_column current_frame current_todo_item |
||||
|
||||
foreach todo_item $::todo::list { |
||||
if {$current_column == 0} { |
||||
pack [set f [frame .[incr current_frame]]] -expand on -fill both -side top |
||||
} |
||||
|
||||
::gui::draw_todo_button "${f}.todo-[incr current_todo_item]" $todo_item |
||||
|
||||
if {$current_column == $columns} { |
||||
set current_column 0 |
||||
} else { |
||||
incr current_column |
||||
} |
||||
} |
||||
|
||||
::gui::draw_statusbar |
||||
::gui::bind_hotkeys |
||||
} |
||||
|
||||
proc draw_todo_button {widget item} { |
||||
# Remove todo markers and useless spaces |
||||
regsub -expanded $::todo::format(marker) $item {} title |
||||
|
||||
pack [ttk::button $widget -text $title -width 1 \ |
||||
-command "::gui::toggle_button_status $widget"] \ |
||||
-expand on -side left -fill both |
||||
|
||||
tooltip::tooltip $widget $title |
||||
|
||||
if {[regexp -expanded $::todo::format(todo) $item]} { |
||||
::gui::set_button_status $widget todo |
||||
} else { |
||||
::gui::set_button_status $widget done |
||||
} |
||||
} |
||||
|
||||
proc draw_statusbar {} { |
||||
pack [frame .statusbar] -fill x |
||||
pack [label .statusbar.status -font {Serif 10} -textvariable ::todo::status -padx 3 -anchor e] -fill x |
||||
} |
||||
|
||||
proc bind_hotkeys {} { |
||||
bind . <Control-q> exit |
||||
bind . <q> exit |
||||
} |
||||
} |
||||
|
||||
# ------------------------------------------------------------------------------ |
||||
|
||||
if {$argc == 0} { |
||||
help |
||||
exit 1 |
||||
} else { |
||||
set filename [lindex $argv 0] |
||||
|
||||
if {![file exists $filename] || ![file writable $filename]} { |
||||
puts stderr "ERROR: File $filename does not exist or it is not writable." |
||||
exit 1 |
||||
} |
||||
} |
||||
|
||||
package require Tk |
||||
package require tooltip |
||||
|
||||
todo::load $filename |
||||
|
||||
gui::show |
||||
|
Loading…
Reference in new issue