mirror of
https://github.com/nadrad/h-m-m.git
synced 2024-06-08 03:52:12 +02:00
2441 lines
49 KiB
PHP
Executable file
2441 lines
49 KiB
PHP
Executable file
#!/usr/bin/env php
|
||
<?php
|
||
// {{{ settings
|
||
|
||
$mm=[];
|
||
|
||
$mm['max_parent_width'] = 25;
|
||
$mm['max_leaf_width'] = 55;
|
||
$mm['line_spacing'] = 1;
|
||
$mm['center_lock'] = false;
|
||
$mm['focus_lock'] = false;
|
||
$mm['initial_depth'] = 1;
|
||
$mm['show_logo'] = 0;
|
||
|
||
$mm['active_node_color'] = "\033[38;5;0m\033[48;5;172m\033[1m";
|
||
$mm['message_color'] = "\033[38;5;0m\033[48;5;141m\033[1m";
|
||
$mm['question_color'] = "\033[38;5;168m";
|
||
|
||
|
||
function load_settings(&$mm)
|
||
{
|
||
global $argv;
|
||
|
||
$conf = $argv[0].'.conf';
|
||
if (!file_exists($conf)) return;
|
||
|
||
$handle = fopen($conf, "r");
|
||
if (!$handle) return;
|
||
|
||
while (($line = fgets($handle)) !== false)
|
||
{
|
||
if (empty(trim($line))) continue;
|
||
|
||
$elements = explode('=',trim($line));
|
||
$setting = trim($elements[0] ?? '');
|
||
$value = trim($elements[1] ?? '');
|
||
|
||
switch ($setting)
|
||
{
|
||
case 'max_parent_width': $mm['max_parent_width'] = max( round($value), width_min ); break;
|
||
case 'max_leaf_width': $mm['max_leaf_width'] = max( round($value), width_min ); break;
|
||
case 'line_spacing': $mm['line_spacing'] = max( round($value), 0 ); break;
|
||
case 'initial_depth': $mm['initial_depth'] = max( round($value), 1 ); break;
|
||
|
||
case 'active_node_color': $mm['active_node_color'] = $value; break;
|
||
case 'message_color': $mm['message_color'] = $value; break;
|
||
|
||
case 'center_lock': $mm['center_lock'] = (bool)($value); break;
|
||
case 'focus_lock': $mm['focus_lock'] = (bool)($value); break;
|
||
}
|
||
}
|
||
|
||
fclose($handle);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ constants and defaults
|
||
|
||
// escape codes: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
||
|
||
$mm['top_left_column'] = 0;
|
||
$mm['top_left_row'] = 0;
|
||
$mm['active_column'] = 0;
|
||
$mm['active_row'] = 0;
|
||
|
||
$mm['terminal_width'] = exec('tput cols');
|
||
$mm['terminal_height'] = exec('tput lines');
|
||
|
||
$mm['win_left'] = 0;
|
||
$mm['win_top'] = 0;
|
||
|
||
$mm['root'] = 2;
|
||
|
||
const logo_color = "\033[38;5;222m";
|
||
const default_color = "\033[0m";
|
||
|
||
const min_indentation = 2;
|
||
const width_tolerance = 1.3;
|
||
const width_min = 15;
|
||
const width_change_factor = 1.2;
|
||
const max_width_padding = 30;
|
||
|
||
const default_leaf_width = 55;
|
||
const default_parent_width = 25;
|
||
|
||
const default_spacing = 1;
|
||
|
||
const width_wider = 0;
|
||
const width_narrower = 1;
|
||
const width_default = 2;
|
||
|
||
const spacing_wider = 0;
|
||
const spacing_narrower = 1;
|
||
const spacing_default = 2;
|
||
|
||
const conn_left_len = 6;
|
||
const conn_right_len = 4;
|
||
|
||
$mm['conn_left'] = str_repeat('─', conn_left_len );
|
||
$mm['conn_right'] = str_repeat('─', conn_right_len - 2 );
|
||
$mm['conn_single'] = str_repeat('─', conn_left_len + conn_right_len - 1 );
|
||
|
||
const vertical_offset = 4;
|
||
|
||
const move_up = 0;
|
||
const move_down = 1;
|
||
const move_left = 2;
|
||
const move_right = 3;
|
||
|
||
const left_padding = 1;
|
||
|
||
const insert_sibling = 0;
|
||
const insert_child = 1;
|
||
|
||
const ctrl_p = "\020";
|
||
const ctrl_c = "\003";
|
||
|
||
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 reset_page = "\033[2J\033[0;0f";
|
||
const reset_color = "\033[0m";
|
||
|
||
const invert_on = "\033[7m";
|
||
const invert_off = "\033[27m";
|
||
|
||
const dim_on = "\033[2m";
|
||
const dim_off = "\033[22m";
|
||
|
||
const line_on = "\033[0m\033[38;5;95m";
|
||
const line_off = "\033[0m";
|
||
|
||
const collapsed_symbol_on = "\033[38;5;215m";
|
||
const collapsed_symbol_off = "\033[0m";
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ alternative screen
|
||
|
||
|
||
function shutdown()
|
||
{
|
||
clear();
|
||
system("tput rmcup && tput cnorm && stty sane");
|
||
exit;
|
||
}
|
||
|
||
register_shutdown_function("shutdown");
|
||
declare(ticks = 1);
|
||
pcntl_signal(SIGINT,"shutdown");
|
||
|
||
|
||
|
||
function set_up_screen()
|
||
{
|
||
// https://www.computerhope.com/unix/ustty.htm
|
||
// https://www.ibm.com/docs/en/aix/7.1?topic=s-stty-command
|
||
|
||
// system('stty cbreak -echo '); // testing
|
||
system('stty cbreak -echo -crterase intr undef && tput smcup'); // production
|
||
|
||
// the first code disables the text cursor and the second one gets rid of the mouse!
|
||
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||
// 'l' disables and 'h' enables
|
||
echo "\033[?25l\033[?9;1000;1001;1002;1003;1004;1007;1005;1006;1015;1016l";
|
||
}
|
||
|
||
|
||
function clear() { echo "\033[2J"; }
|
||
function move($x,$y) { echo "\033[{$y};{$x}f"; }
|
||
function put($x,$y,$t) { echo "\033[{$y};{$x}f{$t}"; }
|
||
|
||
|
||
function mput(&$mm,$x,$y,$s)
|
||
{
|
||
$mm['map'][round($y)]
|
||
= mb_substr( $mm['map'][round($y)], 0, $x)
|
||
. $s
|
||
. mb_substr( $mm['map'][round($y)], $x + mb_strlen($s) );
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ debug
|
||
|
||
function debug($nodes)
|
||
{
|
||
echo " id pr x w y yo h lh clh title\n";
|
||
$i = 0;
|
||
foreach ($nodes as $id=>$data)
|
||
echo
|
||
($i++ % 4 == 0 ? "\n" : '')
|
||
.str_pad($id,3,' ',STR_PAD_LEFT)
|
||
.str_pad($data['parent'],5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['x'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['w'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['y'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['yo'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['h'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['lh'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.str_pad($data['clh'] ?? 'x',5,' ',STR_PAD_LEFT)
|
||
.' '
|
||
.substr($data['title'],0,70)
|
||
."\n"
|
||
;
|
||
echo " id pr x w y yo h lh clh title\n";
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ decode tree
|
||
|
||
function decode_tree($lines, $root_id, $start_id)
|
||
{
|
||
// calculating the indentation shift and cleaning up the special characters
|
||
$indentation_shift = 9999999;
|
||
foreach ($lines as $lid=>$line)
|
||
{
|
||
$lines[$lid] =
|
||
str_replace
|
||
(
|
||
[ "\t", "\n", "\r"]
|
||
,[ " ", " ", " "]
|
||
,$lines[$lid]
|
||
)
|
||
;
|
||
|
||
$indentation = mb_strlen($lines[$lid]) - mb_strlen(ltrim($lines[$lid]));
|
||
$start = mb_substr($lines[$lid], $indentation, 2);
|
||
|
||
if ($start=='* ' || $start=='- ')
|
||
{
|
||
$lines[$lid][$indentation] = ' ';
|
||
$indentation += 2;
|
||
}
|
||
|
||
if (trim($lines[$lid])!='')
|
||
$indentation_shift =
|
||
min
|
||
(
|
||
$indentation_shift
|
||
,$indentation
|
||
)
|
||
;
|
||
}
|
||
|
||
|
||
// building the tree
|
||
|
||
$nodes = [];
|
||
$id = $start_id;
|
||
|
||
$previous_level = 1;
|
||
$level = 1;
|
||
$previous_indentation = 0;
|
||
|
||
$level_parent[1] = $root_id;
|
||
$level_indentation[1] = 0;
|
||
|
||
foreach ($lines as $line)
|
||
if (trim($line)!='')
|
||
{
|
||
// warming up for this round!
|
||
$indentation = mb_strlen($line) - mb_strlen(ltrim($line)) - $indentation_shift;
|
||
|
||
// going one level down
|
||
if ($indentation > $previous_indentation )
|
||
{
|
||
$level = $previous_level + 1;
|
||
$level_indentation[$level] = $indentation;
|
||
}
|
||
|
||
// going one or more levels up
|
||
if ($indentation < $previous_indentation )
|
||
foreach ($level_indentation as $plevel=>$pindentation)
|
||
if ($pindentation == $indentation)
|
||
$level = $plevel;
|
||
|
||
// saving the latest level_parent
|
||
if ($level > $previous_level)
|
||
$level_parent[$level] = $id-1;
|
||
|
||
// done! saving the data
|
||
|
||
$nodes[$id] =
|
||
[
|
||
'title' => trim($line)
|
||
,'parent' => $level_parent[$level]
|
||
];
|
||
|
||
// getting ready for the next round!
|
||
$previous_indentation = $indentation;
|
||
$previous_level = $level;
|
||
$id++;
|
||
}
|
||
|
||
|
||
// setting a few properties that simplify future calculations
|
||
|
||
foreach ($nodes as $id=>$node)
|
||
{
|
||
$nodes[$id]['collapsed'] = false;
|
||
$nodes[$id]['is_leaf'] = true;
|
||
$nodes[$id]['children'] = [];
|
||
}
|
||
|
||
foreach ($nodes as $id=>$node)
|
||
if (isset($nodes[ $node['parent'] ]))
|
||
{
|
||
$nodes[ $node['parent'] ]['is_leaf'] = false;
|
||
$nodes[ $node['parent'] ]['children'][] = $id;
|
||
}
|
||
|
||
return($nodes);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ load_file
|
||
|
||
function load_file(&$mm)
|
||
{
|
||
global $argv;
|
||
|
||
if (!isset($argv[1]))
|
||
{
|
||
load_empty_map($mm);
|
||
return;
|
||
}
|
||
|
||
$mm['filename']=$argv[1];
|
||
|
||
if (!file_exists($argv[1]))
|
||
{
|
||
load_empty_map($mm);
|
||
return;
|
||
}
|
||
|
||
$lines = file($argv[1], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||
|
||
// starting from 2 instead of 1, in case the files doesn't have
|
||
// a single root and we have to inject one. leaving "1" empty
|
||
// won't cause any problems.
|
||
$new_nodes = decode_tree($lines, 0, 2);
|
||
|
||
|
||
// checking to see how many first-level nodes we have
|
||
$first_level_nodes = [];
|
||
foreach ($new_nodes as $id=>$node)
|
||
if ($id!=0 && $node['parent']==0)
|
||
$first_level_nodes[] = $id;
|
||
|
||
$mm['nodes'][0]['parent'] = -1;
|
||
$mm['nodes'][0]['title'] = 'X';
|
||
$mm['nodes'][0]['is_leaf'] = false;
|
||
|
||
if (count($first_level_nodes)>1)
|
||
{
|
||
$mm['root'] = 1;
|
||
|
||
$mm['nodes'][1]['parent'] = 0;
|
||
$mm['nodes'][1]['title'] = 'root';
|
||
$mm['nodes'][1]['is_leaf'] = false;
|
||
$mm['nodes'][1]['children'] = $first_level_nodes;
|
||
$mm['nodes'][0]['children'] = [1];
|
||
|
||
foreach ($new_nodes as $id=>$node)
|
||
if ($node['parent']==0)
|
||
$new_nodes[$id]['parent'] = 1;
|
||
}
|
||
else
|
||
$mm['nodes'][0]['children'] = $first_level_nodes;
|
||
|
||
$mm['nodes'] = $mm['nodes'] + $new_nodes;
|
||
|
||
if (isset($mm['nodes'][1]))
|
||
$mm['active_node']=1;
|
||
else
|
||
$mm['active_node']=2;
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ calculate w, x, lh, and clh
|
||
|
||
|
||
function calculate_x_and_lh(&$mm, $id)
|
||
{
|
||
$node = $mm['nodes'][$id];
|
||
|
||
$mm['nodes'][$id]['x']
|
||
= $mm['nodes'][ $node['parent'] ]['x']
|
||
+ $mm['nodes'][ $node['parent'] ]['w']
|
||
+ conn_left_len
|
||
+ conn_right_len
|
||
+ 1
|
||
+
|
||
(
|
||
$node['parent']==0
|
||
? 1 - conn_right_len - conn_left_len
|
||
: 0
|
||
)
|
||
;
|
||
|
||
$max_width
|
||
= ($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_leaf_width']
|
||
+ !($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_parent_width'];
|
||
|
||
if ( mb_strlen($node['title']) > width_tolerance * $max_width )
|
||
{
|
||
$lines = explode("\n" ,wordwrap($node['title'] ,$max_width));
|
||
|
||
$mm['nodes'][$id]['w'] = 0;
|
||
foreach ($lines as $line)
|
||
$mm['nodes'][$id]['w'] =
|
||
max($mm['nodes'][$id]['w'], trim(mb_strlen($line)));
|
||
|
||
$mm['nodes'][$id]['lh'] = count($lines);
|
||
}
|
||
else
|
||
{
|
||
$mm['nodes'][$id]['w'] = mb_strlen($node['title']);
|
||
$mm['nodes'][$id]['lh'] = 1;
|
||
}
|
||
|
||
$mm['map_width'] =
|
||
max
|
||
(
|
||
$mm['map_width']
|
||
, $mm['nodes'][$id]['x']
|
||
+ $mm['nodes'][$id]['w']
|
||
)
|
||
;
|
||
|
||
$mm['nodes'][$id]['clh'] = 0;
|
||
if (($mm['nodes'][$id]['collapsed'] ?? false) || $mm['nodes'][$id]['is_leaf'])
|
||
$mm['nodes'][$id]['clh'] = $mm['nodes'][$id]['lh'];
|
||
|
||
foreach ($node['children'] as $cid)
|
||
{
|
||
calculate_x_and_lh($mm, $cid);
|
||
$mm['nodes'][$id]['clh'] += $mm['nodes'][$cid]['clh'];
|
||
}
|
||
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ calculate h
|
||
|
||
function calculate_h(&$mm)
|
||
{
|
||
$unfinished = true;
|
||
while ($unfinished)
|
||
{
|
||
$unfinished = false;
|
||
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
|
||
if ($node['is_leaf'] || ($node['collapsed'] ?? false))
|
||
|
||
$mm['nodes'][$id]['h']
|
||
= $mm['line_spacing']
|
||
+ $mm['nodes'][$id]['lh']
|
||
;
|
||
|
||
else
|
||
{
|
||
$h = 0;
|
||
$unready = false;
|
||
|
||
foreach ($node['children'] as $cid)
|
||
if ($mm['nodes'][$cid]['h']>=0)
|
||
$h += $mm['nodes'][$cid]['h'];
|
||
else
|
||
{
|
||
$unready = true;
|
||
break;
|
||
}
|
||
|
||
if ($unready)
|
||
$unfinished = true;
|
||
else
|
||
$mm['nodes'][$id]['h']
|
||
= max
|
||
(
|
||
$h
|
||
, $node['lh']
|
||
+ $mm['line_spacing']
|
||
)
|
||
+ ($mm['nodes'][$id]['parent'] == $mm['root'])
|
||
* $mm['line_spacing'];
|
||
;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ calculate y and yo
|
||
|
||
function calculate_y(&$mm)
|
||
{
|
||
$mm['map_top'] = 0;
|
||
$mm['map_bottom'] = 0;
|
||
$mm['map_height'] = $mm['nodes'][0]['h'];
|
||
|
||
$mm['nodes'][0]['y'] = 0;
|
||
calculate_children_y($mm, 0);
|
||
}
|
||
|
||
function calculate_children_y(&$mm,$pid)
|
||
{
|
||
$y = $mm['nodes'][$pid]['y'];
|
||
|
||
$mm['nodes'][$pid]['yo'] =
|
||
round(
|
||
(
|
||
+ $mm['nodes'][$pid]['h']
|
||
- $mm['nodes'][$pid]['lh']
|
||
)/2
|
||
)
|
||
;
|
||
|
||
if (!($mm['nodes'][$pid]['collapsed'] ?? false))
|
||
foreach ($mm['nodes'][$pid]['children'] as $cid)
|
||
{
|
||
$mm['nodes'][$cid]['y'] = $y;
|
||
|
||
$mm['map_bottom'] =
|
||
max
|
||
(
|
||
$mm['map_bottom']
|
||
,$mm['nodes'][$cid]['lh']
|
||
+$mm['line_spacing']
|
||
+$y
|
||
)
|
||
;
|
||
|
||
$mm['map_top'] = min($mm['map_top'],$y);
|
||
$y += $mm['nodes'][$cid]['h'];
|
||
calculate_children_y($mm,$cid);
|
||
}
|
||
}
|
||
|
||
// }}}
|
||
// {{{ calculate top-down height shift
|
||
|
||
function calculate_height_shift(&$mm, $id, $shift = 0)
|
||
{
|
||
$mm['nodes'][$id]['yo'] += $shift;
|
||
$shift += max(0, round( ($mm['nodes'][$id]['lh'] - $mm['nodes'][$id]['clh'])/2 - 0.5 ));
|
||
|
||
foreach ($mm['nodes'][$id]['children'] as $cid)
|
||
if (!$mm['nodes'][$id]['collapsed'])
|
||
calculate_height_shift($mm, $cid, $shift);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ draw connections on the map
|
||
|
||
function draw_connections(&$mm, $id)
|
||
{
|
||
$node = $mm['nodes'][$id];
|
||
|
||
// if there's no child
|
||
if ($node['is_leaf']) return;
|
||
|
||
// if the node is collapsed
|
||
if ($node['collapsed'] ?? false)
|
||
{
|
||
mput
|
||
(
|
||
$mm
|
||
,$node['x'] + $node['w']+1
|
||
,$node['y'] + $node['yo']
|
||
,' [+]'
|
||
);
|
||
return;
|
||
}
|
||
|
||
// if there's only one child in the same y coordinate
|
||
if (count($node['children'] ?? [])==1)
|
||
{
|
||
$child_id = $node['children'][ array_key_first( $node['children'] ) ];
|
||
$child = $mm['nodes'][ $child_id ];
|
||
|
||
$y1 = round( $node['y'] + $node['yo'] ) + round( ($node['lh'] ) / 2 - 0.6 );
|
||
$y2 = round( $child['y'] + $child['yo'] ) + round( ($child['lh']) / 2 - 0.6 );
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$child['x'] - conn_left_len - conn_right_len
|
||
,min($y1,$y2)
|
||
,$mm['conn_single']
|
||
);
|
||
|
||
if (abs(min($y1,$y2)-$y2)>0)
|
||
{
|
||
for ($yy=min($y1,$y2); $yy<max($y1,$y2); $yy++)
|
||
mput
|
||
(
|
||
$mm
|
||
,$child['x'] - 2
|
||
,$yy
|
||
,'│'
|
||
);
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$child['x'] - 2
|
||
,$y2
|
||
,( $y2 > $y1 ? '╰' : '╭' )
|
||
);
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$child['x'] - 2
|
||
,min($y1,$y2)
|
||
,( $y2 > $y1 ? '╮' : '╯' )
|
||
);
|
||
}
|
||
|
||
draw_connections($mm, $child_id );
|
||
return;
|
||
}
|
||
|
||
// if there's more than one child
|
||
$bottom = 0;
|
||
$bottom_child = 0;
|
||
$top = $mm['map_height'];
|
||
$top_child = 0;
|
||
|
||
foreach ($node['children'] as $cid)
|
||
{
|
||
if ($mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'] > $bottom)
|
||
{
|
||
$bottom = $mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'];
|
||
$bottom_child = $cid;
|
||
}
|
||
|
||
if ($mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'] < $top)
|
||
{
|
||
$top = $mm['nodes'][$cid]['y'] + $mm['nodes'][$cid]['yo'];
|
||
$top_child = $cid;
|
||
}
|
||
}
|
||
|
||
$middle = round($node['y']+$node['yo']) + round($node['lh']/2-0.6);
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_left_len - conn_right_len
|
||
,$middle
|
||
,$mm['conn_left']
|
||
);
|
||
|
||
for ( $i = $top ; $i < $bottom ; $i++ )
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,$i
|
||
,'│'
|
||
);
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,$top
|
||
,'╭'
|
||
.$mm['conn_right']
|
||
);
|
||
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x']-conn_right_len
|
||
,$bottom
|
||
,'╰'
|
||
.$mm['conn_right']
|
||
);
|
||
|
||
if (count($node['children'])>2)
|
||
foreach ($node['children'] as $cid)
|
||
if ($cid!=$top_child && $cid!=$bottom_child)
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$cid]['x']-conn_right_len
|
||
,$mm['nodes'][$cid]['y']
|
||
+$mm['nodes'][$cid]['lh']/2-0.2
|
||
+$mm['nodes'][$cid]['yo']
|
||
,'├'
|
||
.$mm['conn_right']
|
||
);
|
||
|
||
|
||
$existing_char =
|
||
mb_substr
|
||
(
|
||
$mm['map'][$middle]
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,1
|
||
);
|
||
|
||
if ($existing_char=='│')
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,$middle
|
||
,'┤'
|
||
);
|
||
|
||
if ($existing_char=='╭')
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,$middle
|
||
,'┬'
|
||
);
|
||
|
||
if ($existing_char=='├')
|
||
mput
|
||
(
|
||
$mm
|
||
,$mm['nodes'][$top_child]['x'] - conn_right_len
|
||
,$middle
|
||
,'┼'
|
||
);
|
||
|
||
foreach ($node['children'] as $cid)
|
||
draw_connections($mm, $cid);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ add content to the map
|
||
|
||
function add_content_to_the_map(&$mm, $id)
|
||
{
|
||
$node = $mm['nodes'][$id];
|
||
|
||
$max_width
|
||
= ($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_leaf_width']
|
||
+ !($node['is_leaf'] || ($node['collapsed'] ?? false)) * $mm['max_parent_width']
|
||
;
|
||
|
||
if ( mb_strlen($node['title']) > width_tolerance * $max_width)
|
||
$lines =
|
||
explode
|
||
(
|
||
"\n",
|
||
wordwrap
|
||
(
|
||
$node['title'],
|
||
$max_width
|
||
)
|
||
)
|
||
;
|
||
else
|
||
$lines = [$node['title']];
|
||
|
||
$num_lines = count($lines);
|
||
for ( $i=0 ; $i<$num_lines ; $i++ )
|
||
mput
|
||
(
|
||
$mm,
|
||
$node['x'],
|
||
round($node['y']+$node['yo'])+$i,
|
||
$lines[$i].' '
|
||
);
|
||
|
||
if (!($node['collapsed'] ?? false))
|
||
foreach ($node['children'] as $cid)
|
||
add_content_to_the_map($mm,$cid);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ build map
|
||
|
||
function build_map(&$mm)
|
||
{
|
||
// resetting the global values
|
||
$mm['map_width'] = 0;
|
||
$mm['map_height'] = 0;
|
||
$mm['map_top'] = 0;
|
||
$mm['map_bottom'] = 0;
|
||
|
||
// resetting the coordinates
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
{
|
||
$mm['nodes'][$id]['x'] = -1;
|
||
$mm['nodes'][$id]['y'] = -1;
|
||
$mm['nodes'][$id]['h'] = -1;
|
||
$mm['nodes'][$id]['lh'] = -1;
|
||
}
|
||
|
||
$mm['nodes'][0]['x'] = 0;
|
||
$mm['nodes'][0]['w'] = left_padding;
|
||
$mm['nodes'][0]['lh'] = 1;
|
||
|
||
// resetting the map, 1/2
|
||
$mm['map']=[];
|
||
$mm['map_width']=0;
|
||
$mm['map_height']=0;
|
||
$mm['map_top']=0;
|
||
$mm['map_bottom']=0;
|
||
|
||
// calculating the new coordinates
|
||
calculate_x_and_lh($mm,$mm['root']);
|
||
calculate_h($mm);
|
||
calculate_y($mm);
|
||
calculate_height_shift($mm, $mm['root']);
|
||
|
||
// resetting the map, 2/2
|
||
$height = max($mm['map_bottom'],$mm['terminal_height']);
|
||
$blank = str_repeat(' ', $mm['map_width']+$mm['terminal_width']);
|
||
|
||
for ($i=$mm['map_top'] ; $i<=$height ; $i++)
|
||
$mm['map'][$i] = $blank;
|
||
|
||
// building the new map
|
||
draw_connections($mm, $mm['root']);
|
||
add_content_to_the_map($mm, $mm['root']);
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ toggle
|
||
|
||
function toggle(&$mm)
|
||
{
|
||
if ($mm['nodes'][ $mm['active_node'] ]['is_leaf'])
|
||
$mm['active_node'] = $mm['nodes'][ $mm['active_node'] ]['parent'];
|
||
|
||
$mm['nodes'][ $mm['active_node'] ]['collapsed'] =
|
||
!($mm['nodes'][ $mm['active_node'] ]['collapsed'] ?? false);
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ spacing adjuster
|
||
|
||
function adjust_spacing(&$mm, $task)
|
||
{
|
||
if ($task==spacing_wider) $mm['line_spacing']++;
|
||
if ($task==spacing_narrower) $mm['line_spacing'] = max(0, $mm['line_spacing']-1);
|
||
if ($task==spacing_default) $mm['line_spacing'] = default_spacing;
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
message($mm,'Spacing: '.$mm['line_spacing']);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ width adjuster
|
||
|
||
function adjust_width(&$mm, $task)
|
||
{
|
||
if ($task==width_wider)
|
||
{
|
||
$mx = $mm['terminal_width'] - max_width_padding;
|
||
$mm['max_parent_width'] = round(min($mx, max( width_min, $mm['max_parent_width'] * width_change_factor )));
|
||
$mm['max_leaf_width'] = round(min($mx, max( width_min, $mm['max_leaf_width'] * width_change_factor )));
|
||
}
|
||
|
||
if ($task==width_narrower)
|
||
{
|
||
$mm['max_parent_width'] = round(max( width_min, $mm['max_parent_width'] / width_change_factor ));
|
||
$mm['max_leaf_width'] = round(max( width_min, $mm['max_leaf_width'] / width_change_factor ));
|
||
}
|
||
|
||
if ($task==width_default)
|
||
{
|
||
$mm['max_parent_width'] = default_parent_width;
|
||
$mm['max_leaf_width'] = default_leaf_width;
|
||
}
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
|
||
message($mm,'Width: '.$mm['max_parent_width'].' / '.$mm['max_leaf_width']);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ move nodes
|
||
|
||
function push_node_down(&$mm, $id)
|
||
{
|
||
if ($id==0) return;
|
||
|
||
$mm['modified'] = true;
|
||
|
||
if (isset($mm['nodes'][$id+1]))
|
||
push_node_down($mm,$id+1);
|
||
|
||
$mm['nodes'][$id+1] = $mm['nodes'][$id];
|
||
unset($mm['nodes'][$id]);
|
||
|
||
$mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'] =
|
||
array_diff
|
||
(
|
||
$mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'],
|
||
[$id]
|
||
);
|
||
|
||
$mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'] =
|
||
array_push
|
||
(
|
||
$mm['nodes'][ $mm['nodes'][$id+1]['parent'] ]['children'],
|
||
[$id+1]
|
||
);
|
||
|
||
foreach($mm['nodes'][$id+1]['children'] as $cid=>$cdata)
|
||
$mm['nodes'][$cid]['parent'] = $id+1;
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ insert node
|
||
|
||
function insert_node(&$mm, $type)
|
||
{
|
||
|
||
if ($mm['active_node']==$mm['root'])
|
||
$type=insert_child;
|
||
|
||
$mm['modified'] = true;
|
||
|
||
if ($type==insert_sibling)
|
||
$parent_id = $mm['nodes'][ $mm['active_node'] ]['parent'];
|
||
else
|
||
$parent_id = $mm['active_node'];
|
||
|
||
$mm['nodes'][$parent_id]['is_leaf'] = false;
|
||
$mm['nodes'][$parent_id]['collapsed'] = false;
|
||
|
||
$new_id = max(array_keys($mm['nodes'])) + 1;
|
||
|
||
$mm['nodes'][$new_id] =
|
||
[
|
||
'title' => 'NEW',
|
||
'is_leaf' => true,
|
||
'collapsed' => false,
|
||
'children' => [],
|
||
'parent' => $parent_id
|
||
];
|
||
|
||
if ($type==insert_sibling)
|
||
{
|
||
$children = [];
|
||
foreach ($mm['nodes'][$parent_id]['children'] as $child)
|
||
{
|
||
$children[] = $child;
|
||
if ($child==$mm['active_node'])
|
||
$children[] = $new_id;
|
||
}
|
||
$mm['nodes'][$parent_id]['children'] = $children;
|
||
}
|
||
else
|
||
$mm['nodes'][$parent_id]['children'][] = $new_id;
|
||
|
||
$mm['active_node'] = $new_id;
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
|
||
$mm['nodes'][ $mm['active_node'] ]['title']='';
|
||
edit_node($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ edit node
|
||
|
||
function show_line(&$mm, $title, $cursor, $shift)
|
||
{
|
||
$output =
|
||
str_pad
|
||
(
|
||
substr($title,$shift,$mm['terminal_width']-1),
|
||
$mm['terminal_width']
|
||
)
|
||
;
|
||
|
||
$output = substr_replace( $output, invert_off, $cursor-$shift , 0);
|
||
$output = substr_replace( $output, invert_on, $cursor-$shift-1, 0);
|
||
|
||
put(0,$mm['terminal_height'],$mm['active_node_color'].$output);
|
||
}
|
||
|
||
|
||
function edit_node(&$mm, $rewrite = false)
|
||
{
|
||
$title = $rewrite ? '' : $mm['nodes'][ $mm['active_node'] ]['title'];
|
||
if ($mm['active_node']==0 && $title=='root') $title='';
|
||
|
||
$in = '';
|
||
$cursor = strlen($title)+1;
|
||
$shift = max( 0, $cursor - $mm['terminal_width'] );
|
||
|
||
show_line($mm, $title, $cursor, $shift);
|
||
|
||
while(true)
|
||
{
|
||
usleep(10000);
|
||
$in = fread(STDIN, 9);
|
||
|
||
if ($in != '')
|
||
{
|
||
// Esc.
|
||
if ($in=="\033")
|
||
{
|
||
display($mm);
|
||
message($mm, 'Editing cancelled');
|
||
return;
|
||
}
|
||
|
||
// up arrow and home
|
||
elseif ($in=="\033\133\101" || $in=="\033\133\110") $cursor = 1;
|
||
|
||
// right arrow
|
||
elseif ($in=="\033\133\103") $cursor = min( strlen($title)+1, $cursor+1);
|
||
|
||
// down arrow and end
|
||
elseif ($in=="\033\133\102" || $in=="\033\133\106") $cursor = strlen($title)+1;
|
||
|
||
// left arrow
|
||
elseif ($in=="\033\133\104") $cursor = max(1, $cursor-1);
|
||
|
||
// ctrl+left and shift+left
|
||
elseif ($in=="\033\133\061\073\065\104" || $in=="\033\133\061\073\062\104")
|
||
$cursor =
|
||
$cursor < 3
|
||
? 1
|
||
: max
|
||
(
|
||
1,
|
||
(
|
||
strrpos($title,' ',$cursor-strlen($title)-3) !== false
|
||
? strrpos($title,' ',$cursor-strlen($title)-3) + 2
|
||
: 1
|
||
)
|
||
);
|
||
|
||
// ctrl+right and shift+right
|
||
elseif ($in=="\033\133\061\073\065\103" || $in=="\033\133\061\073\062\103")
|
||
$cursor =
|
||
$cursor > strlen($title) -2
|
||
? strlen($title) + 1
|
||
: min
|
||
(
|
||
strlen($title)+1,
|
||
(
|
||
strpos($title,' ',$cursor+1) !== false
|
||
? strpos($title,' ',$cursor+1) + 2
|
||
: strlen($title) + 1
|
||
)
|
||
);
|
||
|
||
// ctrl+backspace
|
||
elseif ($in=="\010")
|
||
{
|
||
$from = mb_strrpos($title, ' ', min(1,$cursor-mb_strlen($title)-3));
|
||
if ($from === false) $from=1;
|
||
$title = mb_substr($title, 0, $from) . mb_substr($title,$cursor-1);
|
||
$cursor = $from+1;
|
||
}
|
||
|
||
// backspace
|
||
elseif ($in=="\177")
|
||
{
|
||
if ($cursor>1)
|
||
{
|
||
$title = substr_replace($title, '', $cursor-2, 1);
|
||
$cursor--;
|
||
}
|
||
}
|
||
|
||
// ctrl+delete
|
||
elseif ($in=="\033\133\63\073\065\176")
|
||
{
|
||
$title = '';
|
||
$cursor = 1;
|
||
}
|
||
|
||
// delete
|
||
elseif ($in=="\033\133\63\176")
|
||
{
|
||
$title = substr_replace($title, '', $cursor-1, 1);
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
// normal characters
|
||
elseif (strlen($in)==1)
|
||
{
|
||
if ($in=="\011") $in=' ';
|
||
$title = substr_replace($title, $in, $cursor-1, 0);
|
||
$cursor++;
|
||
}
|
||
|
||
// adjusting the position and shift
|
||
|
||
$shift = max( 0, $shift, $cursor - $mm['terminal_width'] );
|
||
$shift = min( $shift, $cursor-1 );
|
||
|
||
show_line($mm, $title, $cursor, $shift);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ center active node
|
||
|
||
function center_active_node(&$mm, $only_vertically = false)
|
||
{
|
||
$node = $mm['nodes'][ $mm['active_node'] ];
|
||
|
||
$midx = $node['w']/2 + $node['x'];
|
||
$midy = $node['lh']/2 + $node['y'] + $node['yo'];
|
||
|
||
if (!$only_vertically)
|
||
$mm['win_left'] = max(0, round( $midx - $mm['terminal_width']/2 ) );
|
||
|
||
$mm['win_top'] = round( $midy - $mm['terminal_height']/2 );
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ goto's
|
||
|
||
function go_to_root(&$mm)
|
||
{
|
||
$mm['active_node']=$mm['root'];
|
||
display($mm, true);
|
||
}
|
||
|
||
|
||
function go_to_top(&$mm)
|
||
{
|
||
$yid = 0;
|
||
$y = $mm['map_height'];
|
||
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
if ($node['y']>=0 && $node['y']+$node['yo'] < $y)
|
||
{
|
||
$y = $node['y']+$node['yo'];
|
||
$yid = $id;
|
||
}
|
||
|
||
$mm['active_node'] = $yid;
|
||
display($mm, true);
|
||
}
|
||
|
||
|
||
function go_to_bottom(&$mm)
|
||
{
|
||
$yid = 0;
|
||
$y = 0;
|
||
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
if ($node['y']>=0 && $node['y']+$node['yo'] > $y)
|
||
{
|
||
$y = $node['y']+$node['yo']+$node['lh'];
|
||
$yid = $id;
|
||
}
|
||
|
||
$mm['active_node'] = $yid;
|
||
display($mm, true);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ search
|
||
|
||
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');
|
||
|
||
if (empty($mm['query']))
|
||
{
|
||
display($mm);
|
||
return;
|
||
}
|
||
|
||
if (!next_search_result($mm))
|
||
previous_search_result($mm);
|
||
}
|
||
|
||
function previous_search_result(&$mm)
|
||
{
|
||
$cy
|
||
= $mm['nodes'][ $mm['active_node'] ]['y']
|
||
+ $mm['nodes'][ $mm['active_node'] ]['yo']
|
||
;
|
||
|
||
$ny = -1;
|
||
$nid = -1;
|
||
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
if
|
||
(
|
||
$id != 0 &&
|
||
$node['y'] > -1 &&
|
||
$node['y']+$node['yo'] < $cy &&
|
||
$node['y']+$node['yo'] > $ny &&
|
||
mb_stripos($node['title'],$mm['query'])!==false
|
||
)
|
||
{
|
||
$ny = $node['y'] + $node['yo'];
|
||
$nid = $id;
|
||
}
|
||
|
||
if ($nid<0)
|
||
return false;
|
||
|
||
$mm['active_node'] = $nid;
|
||
display($mm);
|
||
}
|
||
|
||
function next_search_result(&$mm)
|
||
{
|
||
$cy
|
||
= $mm['nodes'][ $mm['active_node'] ]['y']
|
||
+ $mm['nodes'][ $mm['active_node'] ]['yo']
|
||
;
|
||
|
||
$ny = $mm['map_height']+1;
|
||
$nid = -1;
|
||
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
if
|
||
(
|
||
$id != 0 &&
|
||
$node['y'] > -1 &&
|
||
$node['y']+$node['yo'] > $cy &&
|
||
$node['y']+$node['yo'] < $ny &&
|
||
mb_stripos($node['title'],$mm['query'])!==false
|
||
)
|
||
{
|
||
$ny = $node['y'] + $node['yo'];
|
||
$nid = $id;
|
||
}
|
||
|
||
if ($nid<0)
|
||
return false;
|
||
|
||
$mm['active_node'] = $nid;
|
||
display($mm);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ move active node
|
||
|
||
function move_active_node_down(&$mm)
|
||
{
|
||
if ($mm['active_node']==0) return;
|
||
|
||
$mm['modified'] = true;
|
||
|
||
$parent_id = $mm['nodes'][ $mm['active_node'] ]['parent'];
|
||
$children = [];
|
||
$now = false;
|
||
|
||
foreach ($mm['nodes'][ $parent_id ]['children'] as $child)
|
||
{
|
||
if ($child!=$mm['active_node'])
|
||
$children[] = $child;
|
||
|
||
if ($now)
|
||
{
|
||
$children[] = $mm['active_node'];
|
||
$now = false;
|
||
}
|
||
|
||
if ($child==$mm['active_node'])
|
||
$now = true;
|
||
}
|
||
|
||
if ($now)
|
||
$children[] = $mm['active_node'];
|
||
|
||
$mm['nodes'][ $parent_id ]['children'] = $children;
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
function move_active_node_up(&$mm)
|
||
{
|
||
if ($mm['active_node']==0) return;
|
||
|
||
$mm['modified'] = true;
|
||
|
||
$parent_id = $mm['nodes'][ $mm['active_node'] ]['parent'];
|
||
$children = [];
|
||
$now = false;
|
||
$rev_children = array_reverse($mm['nodes'][$parent_id]['children']);
|
||
|
||
foreach ($rev_children as $child)
|
||
{
|
||
if ($child!=$mm['active_node'])
|
||
$children[] = $child;
|
||
|
||
if ($now)
|
||
{
|
||
$children[] = $mm['active_node'];
|
||
$now = false;
|
||
}
|
||
|
||
if ($child==$mm['active_node'])
|
||
$now = true;
|
||
}
|
||
|
||
if ($now)
|
||
$children[] = $mm['active_node'];
|
||
|
||
$mm['nodes'][ $parent_id ]['children'] = array_reverse($children);
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ export html
|
||
|
||
function export_html(&$mm)
|
||
{
|
||
|
||
if (empty($mm['filename']))
|
||
{
|
||
message($mm, "Can't export the map when it doesn't have a file name yet. Save it first.");
|
||
return;
|
||
}
|
||
|
||
$file = fopen($mm['filename'].'.html', "w");
|
||
|
||
if ($file===false)
|
||
{
|
||
message($mm, 'ERROR! Could not save the file');
|
||
return;
|
||
}
|
||
|
||
|
||
fwrite
|
||
(
|
||
$file
|
||
,'<!DOCTYPE html>'
|
||
.'<html lang=en>'
|
||
.'<head>'
|
||
.'<title>'
|
||
.$mm['nodes'][ $mm['root'] ]['title']
|
||
.'</title>'
|
||
.'<meta charset="UTF-8">'
|
||
.'<meta name=viewport content="width=device-width,initial-scale=1,user-scalable=yes">'
|
||
.'<style>'
|
||
.'body { background-color: #222; color: #ddd; font-family: monospace; padding: 0; font-size: 16px; }'
|
||
.'#root {margin:10px 0}'
|
||
.'p:before { content: "━ "; }'
|
||
.'p, summary { padding: 8px; margin: 0; font-size: 16px; }'
|
||
.'details, p { padding-left: 29px; border-left: 3px solid #444; font-size:16px; }'
|
||
.'summary { margin-left: -10px; cursor: pointer; }'
|
||
.'summary:hover, p:hover { color: #fbc531; }'
|
||
.'details:hover, p:hover { border-color: #e1b12c; }'
|
||
.'#source { position: absolute; bottom: 0; left: 0; padding: 5px 15px 8px 15px; margin: 100px 0 0 0; }'
|
||
.'#source { background-color: #333; border: none; box-sizing: border-box;}'
|
||
.'#source > summary { list-style: none; }'
|
||
.'#source[open] { position: static; margin: 100px 0 0 0; font-size: 16px; }'
|
||
.'#map { margin: 40px 30px; }'
|
||
.'</style>'
|
||
.'</head>'
|
||
.'<body>'
|
||
.'<div id=map>'
|
||
.export_html_node($mm, $mm['root'])
|
||
.'</div>'
|
||
.'<details id=source>'
|
||
.'<summary>This is a limited, read-only version of a mind-map created in h-m-m | view source!</summary>'
|
||
.'<pre>'
|
||
.encode_tree($mm,$mm['root'])
|
||
.'</pre>'
|
||
.'</details>'
|
||
.'</body>'
|
||
.'</html>'
|
||
);
|
||
|
||
fclose($file);
|
||
|
||
message($mm, 'Exported as '.$mm['filename'].'.html');
|
||
copy_to_clipboard($mm['filename'].'.html');
|
||
|
||
}
|
||
|
||
|
||
function export_html_node(&$mm, $parent_id)
|
||
{
|
||
if ($mm['nodes'][$parent_id]['children']==[])
|
||
{
|
||
$output =
|
||
"<p>"
|
||
.$mm['nodes'][$parent_id]['title']
|
||
."</p>";
|
||
}
|
||
elseif ($parent_id==$mm['root'])
|
||
{
|
||
$output =
|
||
"<div id=root>"
|
||
.$mm['nodes'][$parent_id]['title']
|
||
."</div>";
|
||
|
||
foreach ($mm['nodes'][$parent_id]['children'] as $cid)
|
||
$output .= export_html_node($mm, $cid);
|
||
}
|
||
else
|
||
{
|
||
$output =
|
||
"<details>"
|
||
."<summary>"
|
||
.$mm['nodes'][$parent_id]['title']
|
||
."</summary>";
|
||
|
||
foreach ($mm['nodes'][$parent_id]['children'] as $cid)
|
||
$output .= export_html_node($mm, $cid);
|
||
|
||
$output .=
|
||
"</details>";
|
||
}
|
||
|
||
return $output;
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ save
|
||
|
||
function save(&$mm, $new_name = false)
|
||
{
|
||
if (empty($mm['filename']))
|
||
$new_name = true;
|
||
|
||
if ($new_name)
|
||
{
|
||
$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: ");
|
||
|
||
system("stty sane");
|
||
$mm['filename'] = trim(readline());
|
||
system('stty cbreak -echo');
|
||
|
||
if ($mm['filename']=='')
|
||
{
|
||
display($mm);
|
||
message($mm, 'Saving cancelled');
|
||
return;
|
||
}
|
||
|
||
$ext = mb_substr( $mm['filename'], mb_strrpos($mm['filename'],'.') + 1);
|
||
|
||
if ($ext!='hmm')
|
||
$mm['filename'] .= '.hmm';
|
||
|
||
display($mm);
|
||
}
|
||
|
||
if (file_exists($mm['filename']) && !is_writable($mm['filename']))
|
||
{
|
||
message($mm, "ERROR! I don't have access to write into \"$mm[filename]\". Use shift+s and set another path and filename.");
|
||
sleep(1);
|
||
return;
|
||
}
|
||
|
||
$file = fopen($mm['filename'], "w");
|
||
|
||
$mm['modified'] = false;
|
||
|
||
if ($file===false)
|
||
{
|
||
message($mm, 'ERROR! Could not save the file');
|
||
$mm['modified'] = true;
|
||
return;
|
||
}
|
||
|
||
fwrite($file, encode_tree($mm, $mm['root']));
|
||
fclose($file);
|
||
|
||
display($mm);
|
||
message($mm, 'Saved '.$mm['filename']);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ message
|
||
|
||
function message(&$mm, $text)
|
||
{
|
||
put
|
||
(
|
||
$mm['terminal_width'] - strlen($text) - 1,
|
||
$mm['terminal_height'],
|
||
$mm['message_color'].' '.$text.' '.reset_color
|
||
);
|
||
|
||
usleep(200000);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ quit
|
||
|
||
function quit(&$mm)
|
||
{
|
||
if (($mm['modified'] ?? false) === false) exit;
|
||
message($mm, "You have unsaved changes. Save them, or use shift+Q to quit without saving.");
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ move_window
|
||
|
||
function move_window(&$mm)
|
||
{
|
||
$node = $mm['nodes'][ $mm['active_node'] ];
|
||
|
||
$x1 = max(0, $node['x'] - conn_right_len - 2);
|
||
$x2 = $node['x'] + $node['w'] + 2;
|
||
$y1 = max(0, $node['y'] + $node['yo'] - vertical_offset);
|
||
$y2 = $y1 + $node['lh'] + vertical_offset*2;
|
||
|
||
$mm['win_left'] = min( $mm['win_left'], $x1);
|
||
$mm['win_left'] = max( $mm['win_left'], $x2 - $mm['terminal_width']);
|
||
|
||
$mm['win_top'] = min( $mm['win_top'], $y1);
|
||
$mm['win_top'] = max( $mm['win_top'], $y2 - $mm['terminal_height']);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ change active node
|
||
|
||
function change_active_node(&$mm, $direction)
|
||
{
|
||
$node = $mm['nodes'][ $mm['active_node'] ];
|
||
|
||
// we go to the child that is closest to the parent node;
|
||
// i.e., closest to the middle.
|
||
|
||
if ($direction==move_right)
|
||
{
|
||
if ($node['is_leaf'] || ($node['collapsed'] ?? false) ) return;
|
||
|
||
$distance = [];
|
||
foreach ($node['children'] as $cid)
|
||
$distance[$cid] =
|
||
abs
|
||
(
|
||
+ $node['y']
|
||
+ $node['yo']
|
||
+ $node['lh']/2
|
||
- $mm['nodes'][$cid]['y']
|
||
- $mm['nodes'][$cid]['yo']
|
||
- $mm['nodes'][$cid]['lh']/2
|
||
)
|
||
;
|
||
|
||
asort($distance);
|
||
|
||
$mm['active_node'] = array_keys($distance)[0];
|
||
|
||
display($mm);
|
||
return;
|
||
}
|
||
|
||
|
||
// no other movement applies to the root element
|
||
if ( $mm['active_node']==0 ) return;
|
||
|
||
|
||
// it can't be easier than moving to the left!
|
||
if ($direction==move_left)
|
||
{
|
||
if ($mm['active_node']==$mm['root']) return;
|
||
$mm['active_node'] = $node['parent'];
|
||
display($mm);
|
||
return;
|
||
}
|
||
|
||
|
||
// for up and down, we'll try to move between siblings first,
|
||
// considering that their order is set based on their list
|
||
// in the parent item.
|
||
|
||
if ($direction==move_up)
|
||
{
|
||
$rchildren = array_reverse($mm['nodes'][ $node['parent'] ]['children']);
|
||
foreach ($rchildren as $cid)
|
||
if ($mm['nodes'][$cid]['y']+$mm['nodes'][$cid]['yo'] < $node['y']+$node['yo'])
|
||
{
|
||
$mm['active_node'] = $cid;
|
||
display($mm);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if ($direction==move_down)
|
||
foreach ($mm['nodes'][ $node['parent'] ]['children'] as $cid)
|
||
if ($mm['nodes'][$cid]['y']+$mm['nodes'][$cid]['yo'] > $node['y']+$node['yo'])
|
||
{
|
||
$mm['active_node'] = $cid;
|
||
display($mm);
|
||
return;
|
||
}
|
||
|
||
|
||
// if it's not possible to move up or down between siblings,
|
||
// we'll measure distance and move to the nearest node.
|
||
// because the goal is to move vertically, and also because
|
||
// the aspect ratio of characters is not 1, there's a factor
|
||
// for y to give it more importance. I've set the amount by
|
||
// trial and erros and doesn't follow an exact logic. It may
|
||
// need refinements in the future.
|
||
|
||
$distance = [];
|
||
|
||
foreach ($mm['nodes'] as $id=>$nd)
|
||
if ($id != $mm['active_node'] && $nd['y']!=-1)
|
||
{
|
||
$dy = $nd['y'] + $nd['yo'] + $nd['lh']/2 - $node['y'] - $node['yo'] - $node['lh']/2;
|
||
if ( ($direction==move_down && $dy>0) || ($direction==move_up && $dy<0) )
|
||
{
|
||
$dx = $nd['x'] + $nd['w']/2 - $node['x'] - $node['w']/2;
|
||
$distance[$id] = pow($dy*15,2) + pow($dx,2);
|
||
}
|
||
}
|
||
|
||
if ($distance==[]) return;
|
||
asort($distance);
|
||
|
||
$mm['active_node'] = array_keys($distance)[0];
|
||
|
||
display($mm);
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ expand
|
||
|
||
function expand(&$mm, $id)
|
||
{
|
||
$mm['nodes'][$id]['collapsed'] = false;
|
||
|
||
foreach ($mm['nodes'][$id]['children'] as $cid)
|
||
expand($mm, $cid);
|
||
}
|
||
|
||
|
||
function expand_all(&$mm)
|
||
{
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
$mm['nodes'][$id]['collapsed'] = false;
|
||
|
||
$mm['show_logo'] = 0;
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ encode tree
|
||
|
||
function encode_tree(&$mm, $id, $exclude_parent = false, $base = 0)
|
||
{
|
||
if (!$exclude_parent)
|
||
$output = str_repeat("\t",$base).$mm['nodes'][$id]['title']."\n";
|
||
else
|
||
$output = '';
|
||
|
||
foreach ($mm['nodes'][$id]['children'] as $cid)
|
||
$output .= encode_tree($mm, $cid, false, $base+1-$exclude_parent);
|
||
|
||
return $output;
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ append
|
||
|
||
function append(&$mm)
|
||
{
|
||
$mm['nodes'][ $mm['active_node'] ]['title'] .=
|
||
' '. str_replace("\n",' ',str_replace("\t",' ',trim(get_from_clipboard())));
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ paste sub-tree
|
||
|
||
function paste_sub_tree(&$mm, $as_sibling )
|
||
{
|
||
|
||
if ($as_sibling && $mm['active_node']==$mm['root']) return;
|
||
|
||
$mm['modified'] = true;
|
||
|
||
if ($as_sibling)
|
||
$parent_id = $mm['nodes'][ $mm['active_node'] ]['parent'];
|
||
else
|
||
$parent_id = $mm['active_node'];
|
||
|
||
$mm['nodes'][$parent_id]['collapsed'] = false;
|
||
$mm['nodes'][ $parent_id ]['is_leaf'] = false;
|
||
|
||
$new_id = 1 + max(array_keys($mm['nodes']));
|
||
|
||
$st =
|
||
decode_tree
|
||
(
|
||
explode("\n",get_from_clipboard()),
|
||
$parent_id,
|
||
$new_id
|
||
)
|
||
;
|
||
|
||
$mm['nodes'] += $st;
|
||
|
||
|
||
// doing it like this, in case the sub-tree has more than
|
||
// one top-level element.
|
||
|
||
foreach ($st as $cid=>$cdata)
|
||
if ($cdata['parent'] == $parent_id)
|
||
$mm['nodes'][ $parent_id ]['children'][] = $cid;
|
||
|
||
|
||
// rearranging the items only if they are pasted as siblings
|
||
// (for pasting as children, it makes sense to have them
|
||
// at the end)
|
||
|
||
if ($as_sibling)
|
||
{
|
||
$sub_roots = [];
|
||
foreach ($st as $cid=>$cdata)
|
||
if ($cdata['parent']==$parent_id)
|
||
$sub_roots[] = $cid;
|
||
|
||
$children = [];
|
||
|
||
foreach ($mm['nodes'][ $parent_id ]['children'] as $child_id)
|
||
{
|
||
if (!in_array($child_id, $sub_roots))
|
||
$children[] = $child_id;
|
||
|
||
if ($child_id == $mm['active_node'])
|
||
$children = array_merge($children, $sub_roots);
|
||
}
|
||
|
||
$mm['nodes'][ $parent_id ]['children'] = $children;
|
||
}
|
||
|
||
|
||
$mm['active_node'] = $new_id;
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ clipboard
|
||
|
||
function copy_to_clipboard($text)
|
||
{
|
||
if (PHP_OS_FAMILY === "Linux") $clip = popen('xclip -selection clipboard','wb');
|
||
elseif (PHP_OS_FAMILY === "Windows") $clip = popen("clip","wb");
|
||
elseif (PHP_OS_FAMILY === "Darwin") $clip = popen('pbcopy','wb');
|
||
|
||
if (!isset($clip)) return;
|
||
|
||
fwrite($clip,$text);
|
||
pclose($clip);
|
||
}
|
||
|
||
function get_from_clipboard()
|
||
{
|
||
if (PHP_OS_FAMILY === "Linux")
|
||
return shell_exec('xclip -out -selection clipboard');
|
||
|
||
elseif (PHP_OS_FAMILY === "Windows")
|
||
return shell_exec('powershell -sta "add-type -as System.Windows.Forms; [windows.forms.clipboard]::GetText()"');
|
||
|
||
elseif (PHP_OS_FAMILY === "Darwin")
|
||
return shell_exec('pbpaste');
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ load empty map
|
||
|
||
function load_empty_map(&$mm)
|
||
{
|
||
if (isset($mm['nodes'])) unset($mm['nodes']);
|
||
|
||
$mm['nodes'][0] = [ 'title'=>'X', 'is_leaf'=>false, 'children'=>[1], 'collapsed'=>false, 'parent'=>-1 ];
|
||
$mm['nodes'][1] = [ 'title'=>'root', 'is_leaf'=>true, 'children'=>[], 'collapsed'=>false, 'parent'=>0 ];
|
||
|
||
$mm['active_node']=1;
|
||
$mm['root']=1;
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ yank
|
||
|
||
function yank_node(&$mm, $exclude_parent = false )
|
||
{
|
||
copy_to_clipboard(encode_tree($mm, $mm['active_node'], $exclude_parent));
|
||
message($mm, 'Item(s) are copied to the clipboard.');
|
||
}
|
||
|
||
// }}}
|
||
// {{{ delete
|
||
|
||
function delete_node(&$mm, $exclude_parent = false )
|
||
{
|
||
if ($mm['active_node']==$mm['root']) $exclude_parent = true;
|
||
|
||
copy_to_clipboard( encode_tree($mm, $mm['active_node'], $exclude_parent) );
|
||
|
||
$mm['modified'] = true;
|
||
|
||
delete_node_internal($mm, $mm['active_node'], $exclude_parent);
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
|
||
message($mm, 'Item(s) are cut and placed into the clipboard.');
|
||
}
|
||
|
||
|
||
function delete_node_internal(&$mm, $active_node, $exclude_parent = false )
|
||
{
|
||
|
||
// taking a shorter approach if it's for the whole tree
|
||
|
||
if ($active_node==$mm['root'] && !$exclude_parent)
|
||
{
|
||
load_empty_map($mm);
|
||
display($mm, true);
|
||
return;
|
||
}
|
||
|
||
|
||
// if it's for a sub-tree, then...
|
||
|
||
delete_children($mm, $active_node);
|
||
|
||
if ($exclude_parent)
|
||
{
|
||
$mm['nodes'][ $active_node ]['is_leaf'] = true;
|
||
$mm['nodes'][ $active_node ]['children'] = [];
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
$parent_id = $mm['nodes'][ $active_node ]['parent'];
|
||
|
||
$previous_sibling = 0;
|
||
$passed = false;
|
||
foreach ($mm['nodes'][$parent_id]['children'] as $cid)
|
||
if ($cid==$active_node)
|
||
{
|
||
if ($previous_sibling!=0) break;
|
||
$passed = true;
|
||
}
|
||
else
|
||
{
|
||
$previous_sibling = $cid;
|
||
if ($passed) break;
|
||
}
|
||
|
||
$mm['nodes'][$parent_id]['children'] =
|
||
array_diff
|
||
(
|
||
$mm['nodes'][$parent_id]['children'],
|
||
[$active_node]
|
||
)
|
||
;
|
||
|
||
if (count($mm['nodes'][$parent_id]['children'])==0)
|
||
$mm['nodes'][$parent_id]['is_leaf'] = true;
|
||
|
||
unset($mm['nodes'][ $active_node ]);
|
||
|
||
if ($mm['nodes'][$parent_id]['is_leaf'])
|
||
$mm['active_node'] = $parent_id;
|
||
else
|
||
$mm['active_node'] = $previous_sibling;
|
||
}
|
||
}
|
||
|
||
|
||
function delete_children(&$mm,$id)
|
||
{
|
||
foreach (($mm['nodes'][$id]['children'] ?? []) as $cid)
|
||
{
|
||
delete_children($mm, $cid);
|
||
unset($mm['nodes'][$cid]);
|
||
}
|
||
}
|
||
|
||
|
||
// }}}
|
||
// {{{ focus
|
||
|
||
function toggle_focus(&$mm)
|
||
{
|
||
$mm['focus_lock'] = !$mm['focus_lock'];
|
||
|
||
message($mm, $mm['focus_lock'] ? 'Focus locked' : 'Focus unlocked');
|
||
|
||
build_map($mm);
|
||
display($mm);
|
||
}
|
||
|
||
function focus(&$mm)
|
||
{
|
||
collapse_siblings($mm, $mm['active_node']);
|
||
expand_siblings($mm, $mm['active_node']);
|
||
}
|
||
|
||
function collapse_siblings(&$mm, $id)
|
||
{
|
||
if ($id <= $mm['root']) return;
|
||
|
||
$parent_id = $mm['nodes'][$id]['parent'];
|
||
|
||
foreach ($mm['nodes'][$parent_id]['children'] as $cid)
|
||
if ($cid!=$id)
|
||
$mm['nodes'][$cid]['collapsed'] = true;
|
||
|
||
collapse_siblings($mm, $parent_id);
|
||
}
|
||
|
||
|
||
function expand_siblings(&$mm, $id)
|
||
{
|
||
if ($mm['nodes'][$id]['is_leaf']) return;
|
||
|
||
$mm['nodes'][$id]['collapsed'] = false;
|
||
foreach ($mm['nodes'][$id]['children'] as $cid)
|
||
expand_siblings($mm, $cid);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ collapse
|
||
|
||
function collapse_other_branches(&$mm)
|
||
{
|
||
if ($mm['active_node'] == $mm['root'])
|
||
return;
|
||
|
||
$branch = find_branch($mm, $mm['active_node']);
|
||
|
||
foreach ($mm['nodes'][ $mm['root'] ]['children'] as $bid)
|
||
if ($bid != $branch)
|
||
$mm['nodes'][$bid]['collapsed'] = true;
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
function collapse_inner(&$mm)
|
||
{
|
||
foreach ($mm['nodes'][ $mm['active_node'] ]['children'] as $cid)
|
||
$mm['nodes'][$cid]['collapsed'] = true;
|
||
|
||
$mm['nodes'][ $mm['active_node'] ]['collapsed'] = false;
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
function find_branch(&$mm, $cid)
|
||
{
|
||
if ($mm['nodes'][$cid]['parent'] == $mm['root'])
|
||
return $cid;
|
||
else
|
||
return find_branch($mm, $mm['nodes'][$cid]['parent']);
|
||
}
|
||
|
||
|
||
function collapse_all(&$mm)
|
||
{
|
||
foreach ($mm['nodes'] as $id=>$node)
|
||
if (!$node['is_leaf'] && $id!=0 && $id!=$mm['root'])
|
||
$mm['nodes'][$id]['collapsed'] = true;
|
||
|
||
$mm['active_node'] = $mm['root'];
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
function collapse(&$mm, $id, $keep)
|
||
{
|
||
if ($mm['nodes'][$id]['is_leaf']) return;
|
||
|
||
if ($keep<=0)
|
||
$mm['nodes'][$id]['collapsed'] = true;
|
||
else
|
||
{
|
||
$mm['nodes'][$id]['collapsed'] = false;
|
||
foreach ($mm['nodes'][$id]['children'] as $cid)
|
||
collapse($mm, $cid, $keep-1);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
function collapse_level(&$mm, $level, $no_display = false)
|
||
{
|
||
collapse($mm, $mm['root'], $level);
|
||
|
||
$id_collapsed = [];
|
||
$current = $mm['active_node'];
|
||
while ($current != $mm['root'])
|
||
{
|
||
$id_collapsed[$current] = $mm['nodes'][$current]['collapsed'];
|
||
$current = $mm['nodes'][$current]['parent'];
|
||
}
|
||
|
||
$id_collapsed = array_reverse( $id_collapsed, true);
|
||
foreach ($id_collapsed as $id=>$collapsed)
|
||
if ($collapsed)
|
||
{
|
||
$mm['active_node'] = $id;
|
||
break;
|
||
}
|
||
|
||
if ($no_display) return;
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ display
|
||
|
||
function display(&$mm, $force_center = false)
|
||
{
|
||
if ($mm['focus_lock'])
|
||
{
|
||
focus($mm);
|
||
build_map($mm);
|
||
}
|
||
|
||
if ($mm['center_lock'] || $force_center)
|
||
center_active_node($mm);
|
||
else
|
||
move_window($mm);
|
||
|
||
$mm['terminal_width'] = exec('tput cols');
|
||
$mm['terminal_height'] = exec('tput lines');
|
||
|
||
$blank = str_repeat(' ', $mm['terminal_width']);
|
||
|
||
|
||
// calculating the coordinates of the active node
|
||
|
||
$x1 =
|
||
max
|
||
(
|
||
0
|
||
,$mm['nodes'][ $mm['active_node'] ]['x']
|
||
-1
|
||
-$mm['win_left']
|
||
)
|
||
;
|
||
|
||
$x2
|
||
= $mm['nodes'][ $mm['active_node'] ]['w']
|
||
+ $x1
|
||
+ 2
|
||
- ($mm['active_node']==0 && left_padding==0)
|
||
;
|
||
|
||
$y1 =
|
||
max
|
||
(
|
||
0
|
||
, $mm['nodes'][ $mm['active_node'] ]['y']
|
||
+ $mm['nodes'][ $mm['active_node'] ]['yo']
|
||
- $mm['win_top']
|
||
)
|
||
;
|
||
|
||
$y2
|
||
= $mm['nodes'][ $mm['active_node'] ]['lh']
|
||
+ $y1
|
||
;
|
||
|
||
|
||
// building the output
|
||
|
||
$output = '';
|
||
|
||
for ( $y = 0 ; $y < $mm['terminal_height'] ; $y++ )
|
||
{
|
||
if (isset($mm['map'][$y+$mm['win_top']]))
|
||
$line =
|
||
mb_substr(
|
||
$mm['map'][$y+$mm['win_top']],
|
||
$mm['win_left'],
|
||
$mm['terminal_width']
|
||
)
|
||
;
|
||
else
|
||
$line = $blank;
|
||
|
||
// this one really depends on (x,y), but after this, the
|
||
// coordinates are not reliable anymore because of the
|
||
// added escape codes.
|
||
|
||
if ( $y >= $y1 && $y < $y2 )
|
||
$line =
|
||
mb_substr($line, 0, $x1)
|
||
.$mm['active_node_color']
|
||
.mb_substr($line, $x1, $x2-$x1)
|
||
.reset_color
|
||
.mb_substr($line, $x2)
|
||
;
|
||
else // styling the codes when the node is not active
|
||
$line =
|
||
mb_ereg_replace
|
||
(
|
||
'\b(.\d)\. '
|
||
,dim_on.'\\1. '.dim_off
|
||
,$line
|
||
)
|
||
;
|
||
|
||
// styling the search results
|
||
|
||
if ($mm['query'] ?? '' != '')
|
||
$line =
|
||
str_ireplace
|
||
(
|
||
$mm['query']
|
||
,invert_on.$mm['query'].invert_off
|
||
,$line
|
||
)
|
||
;
|
||
|
||
// styling the collapsed symbol
|
||
|
||
$line =
|
||
str_replace
|
||
(
|
||
' [+]'
|
||
,' '
|
||
.collapsed_symbol_on
|
||
.'[+]'
|
||
.collapsed_symbol_off
|
||
,$line
|
||
)
|
||
;
|
||
|
||
// styling the lines
|
||
|
||
mb_regex_encoding("UTF-8");
|
||
mb_internal_encoding("UTF-8");
|
||
$line =
|
||
mb_ereg_replace
|
||
(
|
||
'([─-╱]+)'
|
||
,line_on.'\\1'.line_off
|
||
,$line
|
||
)
|
||
;
|
||
|
||
// styling "???"
|
||
|
||
$line =
|
||
str_replace
|
||
(
|
||
'???'
|
||
,$mm['question_color'].'???'.default_color
|
||
,$line
|
||
);
|
||
|
||
// dimming {meta}s
|
||
|
||
$line =
|
||
str_replace
|
||
(
|
||
'{'
|
||
,dim_on.'{'
|
||
,$line
|
||
);
|
||
|
||
$line =
|
||
str_replace
|
||
(
|
||
'}'
|
||
,'}'.dim_off
|
||
,$line
|
||
);
|
||
|
||
// adding the logo
|
||
|
||
if ($mm['show_logo']>0)
|
||
{
|
||
if ($y==2) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' ╭────────────╮ '.default_color;
|
||
elseif ($y==3) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ ┏━ m │ '.default_color;
|
||
elseif ($y==4) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ h ━━┫ │ '.default_color;
|
||
elseif ($y==5) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' │ ┗━━━ m │ '.default_color;
|
||
elseif ($y==6) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' ╰────────────╯ '.default_color;
|
||
elseif ($y==7) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' '.default_color;
|
||
elseif ($y==8) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' hackers '.default_color;
|
||
elseif ($y==9) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' mind '.default_color;
|
||
elseif ($y==10) $line = mb_substr($line, 0, $mm['terminal_width']-19).logo_color. ' map '.default_color;
|
||
}
|
||
|
||
// done!
|
||
$output .= $line;
|
||
}
|
||
|
||
echo reset_page.reset_color.$output;
|
||
$mm['show_logo']--;
|
||
}
|
||
|
||
|
||
|
||
// }}}
|
||
// {{{ monitor key presses
|
||
|
||
function monitor_key_presses(&$mm)
|
||
{
|
||
stream_set_blocking(STDIN,false);
|
||
|
||
while(true)
|
||
{
|
||
usleep(20000);
|
||
$in = fread(STDIN, 16);
|
||
if (empty($in)) continue;
|
||
|
||
switch ($in)
|
||
{
|
||
|
||
case 'b': expand_all($mm); break;
|
||
|
||
case 'c': { center_active_node($mm); display($mm); } break;
|
||
case 'C': { $mm['center_lock'] = !$mm['center_lock']; display($mm); } break;
|
||
case ctrl_c: quit($mm); break;
|
||
|
||
case 'd': delete_node($mm); break;
|
||
case 'D': delete_node($mm, true); break;
|
||
|
||
case 'e': edit_node($mm); break;
|
||
case 'E': edit_node($mm, true); break;
|
||
|
||
case 'f': { focus($mm); build_map($mm); display($mm,true); } break;
|
||
case 'F': toggle_focus($mm); break;
|
||
|
||
case 'g': go_to_top($mm); break;
|
||
case 'G': go_to_bottom($mm); break;
|
||
|
||
case 'h': change_active_node($mm, move_left); break;
|
||
|
||
case 'i': collapse_other_branches($mm); break;
|
||
case 'I': collapse_inner($mm); break;
|
||
|
||
case 'j': change_active_node($mm, move_down); break;
|
||
case 'J': move_active_node_down($mm); break;
|
||
|
||
case 'k': change_active_node($mm, move_up); break;
|
||
case 'K': move_active_node_up($mm); break;
|
||
|
||
case 'l': change_active_node($mm, move_right); break;
|
||
|
||
case 'm': go_to_root($mm); break;
|
||
|
||
case 'n': next_search_result($mm); break;
|
||
case 'N': previous_search_result($mm); break;
|
||
|
||
case 'o': insert_node($mm, insert_sibling); break;
|
||
case 'O': insert_node($mm, insert_child); break;
|
||
|
||
case 'p': paste_sub_tree($mm, false); break;
|
||
case 'P': paste_sub_tree($mm, true); break;
|
||
case ctrl_p: append($mm); break;
|
||
|
||
case 'q': quit($mm); break;
|
||
case 'Q': exit; break;
|
||
|
||
case 's': save($mm); break;
|
||
case 'S': save($mm, true); break;
|
||
|
||
case 'U': debug($mm['nodes']); break;
|
||
|
||
case 'v': collapse_all($mm); break;
|
||
|
||
case 'w': adjust_width($mm, width_wider); break;
|
||
case 'W': adjust_width($mm, width_narrower); break;
|
||
|
||
case 'x': export_html($mm); break;
|
||
|
||
case 'y': yank_node($mm); break;
|
||
case 'Y': yank_node($mm, true); break;
|
||
|
||
case 'Z': adjust_spacing($mm, spacing_wider); break;
|
||
case 'z': adjust_spacing($mm, spacing_narrower); break;
|
||
|
||
case arr_down: change_active_node($mm, move_down); break;
|
||
case arr_right: change_active_node($mm, move_right); break;
|
||
case arr_up: change_active_node($mm, move_up); break;
|
||
case arr_left: change_active_node($mm, move_left); break;
|
||
|
||
case '1': collapse_level($mm, 1); break;
|
||
case '2': collapse_level($mm, 2); break;
|
||
case '3': collapse_level($mm, 3); break;
|
||
case '4': collapse_level($mm, 4); break;
|
||
case '5': collapse_level($mm, 5); break;
|
||
case '6': collapse_level($mm, 6); break;
|
||
case '7': collapse_level($mm, 7); break;
|
||
case '8': collapse_level($mm, 8); break;
|
||
case '9': collapse_level($mm, 9); break;
|
||
|
||
case '~': go_to_root($mm); break;
|
||
case ' ': toggle($mm); break;
|
||
case '/': search($mm); break;
|
||
|
||
case "\n": insert_node($mm, insert_sibling); break;
|
||
case "\t": insert_node($mm, insert_child); break;
|
||
|
||
}
|
||
|
||
// move(1,1);
|
||
// for ($i=0; $i<strlen($in); $i++)
|
||
// echo base_convert(ord($in[$i]),10,8).' ';
|
||
// echo ' ';
|
||
}
|
||
}
|
||
|
||
// }}}
|
||
// {{{ main
|
||
|
||
set_up_screen();
|
||
load_settings($mm);
|
||
clear();
|
||
load_file($mm);
|
||
|
||
collapse_all($mm);
|
||
collapse_level($mm, $mm['initial_depth'], true);
|
||
|
||
build_map($mm);
|
||
center_active_node($mm);
|
||
display($mm);
|
||
monitor_key_presses($mm);
|
||
|
||
|
||
// }}}
|