diff --git a/h-m-m b/h-m-m index 2132163..66c9a3c 100755 --- a/h-m-m +++ b/h-m-m @@ -19,6 +19,7 @@ $mm['question_color'] = "\033[38;5;168m"; $mm['changes'] = []; $mm['change_active_node'] = []; $mm['change_index'] = 0; +$mm['change_max_steps'] = 24; mb_regex_encoding("UTF-8"); mb_internal_encoding("UTF-8"); @@ -49,6 +50,8 @@ function load_settings(&$mm) case 'line_spacing': $mm['line_spacing'] = max( round($value), 0 ); break; case 'initial_depth': $mm['initial_depth'] = max( round($value), 1 ); break; + case 'undo_steps': $mm['change_max_steps'] = max( round($value), 0 ); break; + case 'active_node_color': $mm['active_node_color'] = $value; break; case 'message_color': $mm['message_color'] = $value; break; @@ -125,12 +128,16 @@ const insert_child = 1; const ctrl_p = "\020"; const ctrl_c = "\003"; const ctrl_r = "\022"; +const ctrl_f = "\006"; +const ctrl_v = "\026"; const arr_down = "\033\133\102"; const arr_right = "\033\133\103"; const arr_up = "\033\133\101"; const arr_left = "\033\133\104"; +const del = "\033\133\063\176"; + const reset_page = "\033[2J\033[0;0f"; const reset_color = "\033[0m"; @@ -315,11 +322,16 @@ function decode_tree($lines, $root_id, $start_id) foreach ($lines as $lid=>$line) { $lines[$lid] = - str_replace + mb_ereg_replace ( - [ "\t", "\n", "\r", BOM ] - ,[ " ", " ", " ", "" ] - ,$lines[$lid] + "[\000-\010\013-\037\177".BOM."]" + ,'' + ,str_replace + ( + [ "\t", "\n", "\r" ] + ,[ " ", " ", " " ] + ,$lines[$lid] + ) ) ; @@ -1015,7 +1027,6 @@ function push_node_down(&$mm, $id) if ($id==0) return; push_change($mm); - $mm['modified'] = true; if (isset($mm['nodes'][$id+1])) @@ -1052,6 +1063,7 @@ function insert_node(&$mm, $type) if ($mm['active_node']==$mm['root']) $type=insert_child; + push_change($mm); $mm['modified'] = true; if ($type==insert_sibling) @@ -1099,46 +1111,10 @@ function insert_node(&$mm, $type) // }}} -// {{{ edit node +// {{{ magic readline! -function show_line(&$mm, $title, $cursor, $shift) +function magic_readline(&$mm, $title) { - $output = mb_substr($title,$shift,$mm['terminal_width']-1); - $output .= str_repeat( ' ' ,$mm['terminal_width'] - mb_strlen($output) ); - - // showing the cursor - $output = - mb_substr - ( - $output - ,0 - ,$cursor-$shift-1 - ) - .invert_on - .mb_substr - ( - $output - ,$cursor-$shift-1 - ,1 - ) - .invert_off - .mb_substr - ( - $output - ,$cursor-$shift - ); - - put(0,$mm['terminal_height'],$mm['active_node_color'].$output); -} - - -function edit_node(&$mm, $rewrite = false) -{ - push_change($mm); - - $title = $rewrite ? '' : $mm['nodes'][ $mm['active_node'] ]['title']; - if ($mm['active_node']==0 && $title=='root') $title=''; - $in = ''; $cursor = mb_strlen($title)+1; $shift = max( 0, $cursor - $mm['terminal_width'] ); @@ -1147,8 +1123,12 @@ function edit_node(&$mm, $rewrite = false) while(true) { - usleep(10000); - $in = fread(STDIN, 9); + usleep(5000); + $in = fread(STDIN, 66666); + // normally, the longest sequence we have is 13 bytes, + // but if ctrl+shift+v is used, the whole text will be passed! + // In other words, we don't receive a ctrl+shift+v input, + // but the actual content. Was that a confusing behavior? Of course!!! if ($in != '') { @@ -1157,7 +1137,7 @@ function edit_node(&$mm, $rewrite = false) { display($mm); message($mm, 'Editing cancelled'); - return; + return false; } // up arrow and home @@ -1262,32 +1242,19 @@ function edit_node(&$mm, $rewrite = false) // enter elseif ($in=="\012") - { - $title = trim($title); - $mm['nodes'][ $mm['active_node'] ]['title'] = $title; - $original['nodes'][ $mm['active_node'] ]['title'] = $title; - $mm['modified'] = true; - build_map($mm); - display($mm); - return; - } + return trim($title); // ctrl+v - elseif ($in=="\026") + elseif ($in==ctrl_v) { $content = trim ( str_replace ( - "\n", - " ", - str_replace - ( - "\t", - " ", - get_from_clipboard($mm) - ) + ["\n", "\r", "\t"] + ,[" ", "", " " ] + ,get_from_clipboard($mm) ) ); @@ -1326,10 +1293,13 @@ function edit_node(&$mm, $rewrite = false) ( $title ,$cursor-1 - ); + ) + ; - $title = str_replace(BOM,'',$title); - $cursor++; + $title = mb_ereg_replace("[\000-\010\013-\037\177".BOM."]",'',$title); + $cursor += mb_strlen($in); + // the input content can be longer than one character if + // the user uses ctrl+shift+v to paste. } // adjusting the position and shift @@ -1343,6 +1313,65 @@ function edit_node(&$mm, $rewrite = false) } +function show_line(&$mm, $title, $cursor, $shift) +{ + $output = mb_substr($title,$shift,$mm['terminal_width']-1); + $output .= str_repeat( ' ' ,$mm['terminal_width'] - mb_strlen($output) ); + + // showing the cursor + $output = + mb_substr + ( + $output + ,0 + ,$cursor-$shift-1 + ) + .invert_on + .mb_substr + ( + $output + ,$cursor-$shift-1 + ,1 + ) + .invert_off + .mb_substr + ( + $output + ,$cursor-$shift + ); + + put(0,$mm['terminal_height'],$mm['active_node_color'].$output); +} + + + +// }}} +// {{{ edit node + +function edit_node(&$mm, $rewrite = false) +{ + $title = $rewrite ? '' : $mm['nodes'][ $mm['active_node'] ]['title']; + if ($mm['active_node']==0 && $title=='root') $title=''; + + $output = magic_readline($mm, $title); + + if ($output === false) + { + display($mm); + message($mm, 'Editing cancelled'); + return; + } + + $mm['nodes'][ $mm['active_node'] ]['title'] = $output; + + push_change($mm); + $mm['modified'] = true; + + build_map($mm); + display($mm); +} + + // }}} // {{{ center active node @@ -1411,12 +1440,7 @@ function go_to_bottom(&$mm) function search(&$mm) { - put(0,$mm['terminal_height'],$mm['active_node_color'].str_repeat(' ',$mm['terminal_width'])); - move(0,$mm['terminal_height']); - - system("stty sane"); - $mm['query'] = readline('Search for: '); - system('stty cbreak -echo'); + $mm['query'] = magic_readline($mm,''); if (empty($mm['query'])) { @@ -1426,6 +1450,7 @@ function search(&$mm) if (!next_search_result($mm)) previous_search_result($mm); + } function previous_search_result(&$mm) @@ -1453,7 +1478,10 @@ function previous_search_result(&$mm) } if ($nid<0) + { + display($mm); return false; + } $mm['active_node'] = $nid; display($mm); @@ -1499,7 +1527,6 @@ function move_active_node_down(&$mm) if ($mm['active_node']==0) return; push_change($mm); - $mm['modified'] = true; $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; @@ -1536,7 +1563,6 @@ function move_active_node_up(&$mm) if ($mm['active_node']==0) return; push_change($mm); - $mm['modified'] = true; $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; @@ -1683,26 +1709,18 @@ function export_html_node(&$mm, $parent_id) function save(&$mm, $new_name = false) { - if (empty($mm['filename'])) - $new_name = true; - - if ($new_name) + if ($new_name || empty($mm['filename'])) { - $path = exec('pwd'); - put(0,$mm['terminal_height'],$mm['active_node_color'].str_repeat(' ',$mm['terminal_width'])); - put(0,$mm['terminal_height']," $path -- new path and file name: "); + $new_name = magic_readline($mm, empty($mm['filename']) ? exec('pwd') : $mm['filename']); - system("stty sane"); - $mm['filename'] = trim(readline()); - system('stty cbreak -echo'); - - if ($mm['filename']=='') + if ($new_name === false) { display($mm); message($mm, 'Saving cancelled'); return; } + $mm['filename'] = $new_name; $ext = mb_substr( $mm['filename'], mb_strrpos($mm['filename'],'.') + 1); if ($ext!='hmm') @@ -1791,7 +1809,18 @@ function push_change(&$mm) { // flush any redo chain while(count($mm['changes']) > $mm['change_index']) + { array_pop($mm['changes']); + array_pop($mm['change_active_node']); + } + + // removing the old history if it's getting bigger than the maximum + if (count($mm['changes']) >= $mm['change_max_steps']) + { + array_shift($mm['changes']); + array_shift($mm['change_active_node']); + $mm['change_index']--; + } array_push($mm['changes'], $mm['nodes']); array_push($mm['change_active_node'], $mm['active_node']); @@ -1986,7 +2015,15 @@ function encode_tree(&$mm, $id, $exclude_parent = false, $base = 0) function append(&$mm) { $mm['nodes'][ $mm['active_node'] ]['title'] .= - ' '. str_replace("\n",' ',str_replace("\t",' ',trim(get_from_clipboard($mm)))); + ' ' + .str_replace + ( + ["\n","\r","\t"] + ,[' ','', ' '] + ,trim(get_from_clipboard($mm)) + ) + ; + build_map($mm); display($mm); } @@ -2001,10 +2038,6 @@ function paste_sub_tree(&$mm, $as_sibling ) if ($as_sibling && $mm['active_node']==$mm['root']) return; - push_change($mm); - - $mm['modified'] = true; - if ($as_sibling) $parent_id = $mm['nodes'][ $mm['active_node'] ]['parent']; else @@ -2024,8 +2057,12 @@ function paste_sub_tree(&$mm, $as_sibling ) ) ; - $mm['nodes'] += $st; + if ($st==[]) return; + push_change($mm); + $mm['modified'] = true; + + $mm['nodes'] += $st; // doing it like this, in case the sub-tree has more than // one top-level element. @@ -2083,7 +2120,14 @@ function copy_to_clipboard(&$mm, $text) function get_from_clipboard(&$mm) { - return str_replace(BOM,'',shell_exec($mm['clipboard']['read'])); + return + mb_ereg_replace + ( + "[\000-\010\013-\037\177".BOM."]" + ,'' + ,shell_exec($mm['clipboard']['read']) + ) + ; } @@ -2114,14 +2158,15 @@ function yank_node(&$mm, $exclude_parent = false ) // }}} // {{{ delete -function delete_node(&$mm, $exclude_parent = false ) +function delete_node(&$mm, $exclude_parent = false, $dont_copy_to_clipboard = false ) { + if ($mm['active_node']==$mm['root']) + $exclude_parent = true; + + if (!$dont_copy_to_clipboard) + copy_to_clipboard($mm, encode_tree($mm, $mm['active_node'], $exclude_parent) ); + push_change($mm); - - if ($mm['active_node']==$mm['root']) $exclude_parent = true; - - copy_to_clipboard($mm, encode_tree($mm, $mm['active_node'], $exclude_parent) ); - $mm['modified'] = true; delete_node_internal($mm, $mm['active_node'], $exclude_parent); @@ -2129,7 +2174,8 @@ function delete_node(&$mm, $exclude_parent = false ) build_map($mm); display($mm); - message($mm, 'Item(s) are cut and placed into the clipboard.'); + if (!$dont_copy_to_clipboard) + message($mm, 'Item(s) are cut and placed into the clipboard.'); } @@ -2565,6 +2611,7 @@ function monitor_key_presses(&$mm) case 'd': delete_node($mm); break; case 'D': delete_node($mm, true); break; + case del: delete_node($mm, false, true); break; case 'e': edit_node($mm); break; case 'E': edit_node($mm, true); break; @@ -2644,7 +2691,10 @@ function monitor_key_presses(&$mm) case '~': go_to_root($mm); break; case ' ': toggle($mm); break; + case '/': search($mm); break; + case '?': search($mm); break; + case ctrl_f: search($mm); break; case "\n": insert_node($mm, insert_sibling); break; case "\t": insert_node($mm, insert_child); break; diff --git a/readme.md b/readme.md index cfd174a..fe172af 100644 --- a/readme.md +++ b/readme.md @@ -10,19 +10,20 @@ Adding, removing, and editing nodes: -* `o` or `enter` - create a new sibling to the active node -* `O` or `tab` - create a new child for the active node +* `o` or `Enter` - create a new sibling to the active node +* `O` or `Tab` - create a new child for the active node * `y` - yanks (copies) the active node and its descendants * `Y` - yanks (copies) the descendants of the active node * `d` - deletes (cuts) the active node and its descendants * `D` - deletes (cuts) the descendants of the active node +* `Delete` - deletes the active node and its descendants without putting them in the clipboard * `p` - pastes as descendants of the active node * `P` - pastes as siblings of the active node -* `ctrl+p` - appends the clipboard text at the end of the active node's title +* `Ctrl+p` - appends the clipboard text at the end of the active node's title * `e`, `i`, or `a` - edits the active node * `E`, `I`, or `A` - edits the active node, ignoring the existing text * `u` - undo -* `ctrl+r` - redo +* `Ctrl+r` - redo Relative navigating and moving: @@ -47,7 +48,7 @@ Adjusting the view: Collapsing and expanding: -* `space` - toggles the active node +* `Space` - toggles the active node * `v` - collapses everything other than the first-level nodes * `b` - expands all nodes * `1` to `9` - collapse the nth level and expand those before @@ -58,7 +59,7 @@ Collapsing and expanding: Search: -* `/` - searches for a phrase +* `/`, `?`, or `Ctrl+f` - searches for a phrase * `n` - goes to the next search result * `N` - goes to the previous search result @@ -70,6 +71,21 @@ Save, export, and quit: * `q` - quits (if the changes were already saved) * `Q` - quits, ignoring the changes +In the text editor: + +* `↓` - move the cursor to the end of the line +* `↑` - move the cursor to the beginning of the line +* `←` or `Home` - move the cursor to the left +* `→` or `End` - move the cursor to the right +* `Ctrl+Left` or `Shift+Left` - move cursor to the previous word +* `Ctrl+Right` or `Shift+right` - move cursor to the next word +* `Delete` - delete character +* `Ctrl+Delete` - delete word +* `Backspace` - delete previous character +* `ctrl+Backspace` - delete previous word +* `Ctrl+v` or `Ctrl+Shift+v` - paste +* `Esc` - cancel editing +* `Enter` - wanna guess? ;) # Configuration @@ -84,6 +100,7 @@ You can create an `h-m-m.conf` file in the same directory as the application and message_color = "\033[38;5;0m\033[48;5;141m\033[1m" center_lock = false focus_lock = false + undo_steps = 24 The colors are ASCII escape codes.