2021-10-02 21:38:26 +00:00
from kivy . uix . label import Label
from kivy . core . window import Window
2021-10-02 22:08:57 +00:00
from kivy . graphics import Color , Rectangle
2021-10-16 06:09:53 +00:00
import glob
import os . path
2021-10-20 04:55:20 +00:00
import filecheck
2021-10-02 21:38:26 +00:00
class Command_prompt ( Label ) :
def __init__ ( self , app , * * kwargs ) :
# app is used to share information between widgets
self . app = app
# insert mode
self . insert = False
2021-10-16 06:46:06 +00:00
# width of a letter
self . char_width = 9
2021-10-02 22:08:57 +00:00
# cursor position
self . cursor = 0
# the text, with no color information
self . raw_text = " "
2021-10-16 20:48:42 +00:00
# a one-time message that shows up over the bar
self . message = " "
2021-10-15 23:36:29 +00:00
# array of command with arguments
self . argv = [ ]
2021-10-16 16:16:30 +00:00
# display characters from window_offset onwards (for text wrapping)
self . window_offset = 0
2021-10-02 21:38:26 +00:00
# init Label
super ( Command_prompt , self ) . __init__ ( * * kwargs )
self . keyboard = Window . request_keyboard ( None , self , " text " )
self . keyboard . bind ( on_textinput = self . on_textinput , on_key_down = self . on_key_down )
2021-10-02 22:08:57 +00:00
self . draw ( )
2021-10-16 00:23:11 +00:00
2021-10-02 22:08:57 +00:00
def draw ( self ) :
2021-10-20 04:37:09 +00:00
self . canvas . before . clear ( )
2021-10-16 20:48:42 +00:00
# background
with self . canvas . before :
Color ( 0 , 0 , 0 )
Rectangle ( pos = self . pos , size = self . size )
# if message is not empty, draw message instead
if self . message != " " :
# do not wrap
self . text = self . message [ : min ( len ( self . message ) , int ( self . width / self . char_width ) ) ]
self . message = " "
return
2021-10-16 16:16:30 +00:00
# wrap text
window_size = int ( self . width / self . char_width )
if self . cursor > = self . window_offset + window_size :
self . window_offset = self . cursor - window_size + 1
elif self . cursor < self . window_offset :
self . window_offset = self . cursor
# do not stop right before ':'
if self . window_offset == 1 :
self . window_offset = 0
2021-10-16 20:48:42 +00:00
# cursor
2021-10-02 22:08:57 +00:00
with self . canvas . before :
Color ( 1 , 1 , 1 )
2021-10-16 16:16:30 +00:00
# wrap cursor position
Rectangle ( pos = ( self . pos [ 0 ] + ( self . cursor - self . window_offset ) * self . char_width , self . pos [ 1 ] ) , size = ( self . char_width , self . height ) )
2021-10-02 22:08:57 +00:00
# make text under cursor black
2021-10-16 16:16:30 +00:00
end = min ( len ( self . raw_text ) , self . window_offset + window_size )
if self . cursor < end :
self . text = self . raw_text [ self . window_offset : self . cursor ] + " [color=000] " + self . raw_text [ self . cursor ] + " [/color] " + self . raw_text [ self . cursor + 1 : end ]
2021-10-02 22:08:57 +00:00
else :
2021-10-16 16:16:30 +00:00
self . text = self . raw_text [ self . window_offset : end ]
2021-10-02 22:08:57 +00:00
2021-10-16 00:23:11 +00:00
2021-10-02 21:38:26 +00:00
def on_key_down ( self , keyboard , keycode , text , modifiers ) :
2021-10-16 06:09:53 +00:00
#print(keycode,text,modifiers)
2021-10-02 21:38:26 +00:00
if self . insert :
2021-10-16 00:23:11 +00:00
# process modifiers
mods = self . process_modifiers ( modifiers )
2021-10-15 23:57:03 +00:00
# process command
2021-10-02 21:38:26 +00:00
if keycode [ 1 ] == " enter " :
self . insert = False
2021-10-16 20:19:22 +00:00
self . run_command ( )
2021-10-02 22:08:57 +00:00
self . set_text ( " " )
2021-10-16 23:19:39 +00:00
elif keycode [ 1 ] == " escape " :
self . insert = False
self . set_text ( " " )
2021-10-15 23:57:03 +00:00
# move
2021-10-02 22:08:57 +00:00
elif keycode [ 1 ] == " left " :
2021-10-09 20:43:46 +00:00
self . move_cursor_relative ( - 1 )
2021-10-02 22:08:57 +00:00
elif keycode [ 1 ] == " right " :
2021-10-09 20:43:46 +00:00
self . move_cursor_relative ( 1 )
2021-10-16 00:23:11 +00:00
elif mods == [ ' c ' ] and text == " a " :
2021-10-02 22:08:57 +00:00
self . move_cursor ( 1 )
2021-10-16 00:23:11 +00:00
elif mods == [ ' c ' ] and text == " e " :
2021-10-09 20:43:46 +00:00
self . move_cursor ( len ( self . raw_text ) )
2021-10-02 21:38:26 +00:00
2021-10-15 23:57:03 +00:00
# delete
elif keycode [ 1 ] == " backspace " :
self . backspace ( )
elif keycode [ 1 ] == " delete " :
self . delete_forward ( )
2021-10-16 00:23:11 +00:00
elif mods == [ ' c ' ] and text == " k " :
2021-10-15 23:57:03 +00:00
self . delete_all_forward ( )
2021-10-16 06:09:53 +00:00
# tab completion
elif keycode [ 1 ] == " tab " :
self . tab_complete ( )
2021-10-02 21:38:26 +00:00
def on_textinput ( self , window , text ) :
2021-10-16 20:48:42 +00:00
# reset status_bar
self . app . status_bar . draw ( )
2021-10-02 21:38:26 +00:00
if self . insert :
2021-10-02 22:08:57 +00:00
self . append_text_at_cursor ( text )
2021-10-02 21:38:26 +00:00
elif text == ' : ' :
2021-10-02 22:08:57 +00:00
self . append_text_at_cursor ( text )
2021-10-02 21:38:26 +00:00
self . insert = True
2021-10-02 22:08:57 +00:00
2021-10-16 00:23:11 +00:00
# get modifiers from list
# returns an alphabetically ordered array of characters
# 'a' is alt
# 'c' is ctrl
# 'm' is meta
# 's' is shift
# ignore all others
def process_modifiers ( self , modifiers ) :
out = [ ]
for mod in modifiers :
if mod == " alt " :
out . append ( ' a ' )
elif mod == " ctrl " :
out . append ( ' c ' )
elif mod == " meta " :
out . append ( ' m ' )
elif mod == " shift " :
out . append ( ' s ' )
out . sort ( )
return out
2021-10-02 22:08:57 +00:00
# append to text at position of cursor
def append_text_at_cursor ( self , text ) :
if self . cursor == len ( self . raw_text ) :
self . raw_text + = text
else :
self . raw_text = self . raw_text [ : self . cursor ] + text + self . raw_text [ self . cursor : ]
self . cursor + = len ( text )
self . draw ( )
2021-10-15 23:57:03 +00:00
# delete before cursor
def backspace ( self ) :
# do not delete leading ':'
if self . cursor == 1 :
return
if self . cursor == len ( self . raw_text ) :
self . raw_text = self . raw_text [ : self . cursor - 1 ]
else :
self . raw_text = self . raw_text [ : self . cursor - 1 ] + self . raw_text [ self . cursor : ]
self . cursor - = 1
self . draw ( )
# delete after cursor
def delete_forward ( self ) :
if self . cursor == len ( self . raw_text ) :
return
else :
self . raw_text = self . raw_text [ : self . cursor ] + self . raw_text [ self . cursor + 1 : ]
self . draw ( )
# delete until end of line(self):
def delete_all_forward ( self ) :
if self . cursor == len ( self . raw_text ) :
return
else :
self . raw_text = self . raw_text [ : self . cursor ]
self . draw ( )
2021-10-02 22:08:57 +00:00
# set text in prompt
def set_text ( self , text ) :
self . raw_text = text
self . cursor = len ( text )
self . draw ( )
2021-10-16 00:23:11 +00:00
2021-10-02 22:08:57 +00:00
# move cursor n steps to right (left if negative)
2021-10-09 20:43:46 +00:00
def move_cursor_relative ( self , n ) :
2021-10-02 22:08:57 +00:00
if n == 0 or ( n > 0 and self . cursor == len ( self . raw_text ) ) or ( n < 0 and self . cursor == 0 ) :
return
2021-10-09 20:43:46 +00:00
self . cursor = max ( 1 , min ( len ( self . raw_text ) , self . cursor + n ) )
self . draw ( )
# move cursor to absolute position
def move_cursor ( self , n ) :
self . cursor = max ( 1 , min ( len ( self . raw_text ) , n ) )
2021-10-02 22:08:57 +00:00
self . draw ( )
2021-10-15 23:36:29 +00:00
2021-10-16 00:23:11 +00:00
2021-10-15 23:36:29 +00:00
# parse text as argv
2021-10-16 06:09:53 +00:00
def parse_argv ( self ) :
2021-10-15 23:36:29 +00:00
# init
self . argv = [ " " ]
2021-10-16 06:09:53 +00:00
# return position of cursor
cursor = ( 0 , 0 )
2021-10-15 23:36:29 +00:00
# whether inside quotes
single_quoted = False
double_quoted = False
# whether after '\'
backslashed = False
# start after ':'
# proceed one character at a time
for i in range ( 1 , len ( self . raw_text ) ) :
char = self . raw_text [ i ]
# split argument
if single_quoted == False and double_quoted == False and backslashed == False and char == ' ' :
# new argv
self . argv . append ( " " )
# quotes or '\'
elif double_quoted == False and backslashed == False and char == ' \' ' :
single_quoted = not single_quoted
elif single_quoted == False and backslashed == False and char == ' " ' :
double_quoted = not double_quoted
elif single_quoted == False and backslashed == False and char == ' \\ ' :
backslashed = True
# write character
else :
self . argv [ len ( self . argv ) - 1 ] + = char
# reset backslash
backslashed = False
2021-10-16 06:09:53 +00:00
# record position of cursor
if self . cursor == i + 1 :
cursor = ( len ( self . argv ) - 1 , len ( self . argv [ len ( self . argv ) - 1 ] ) )
2021-10-15 23:36:29 +00:00
2021-10-16 06:09:53 +00:00
return cursor
# tab completion
def tab_complete ( self ) :
# parse argv
cursor = self . parse_argv ( )
# write and edit commands
if self . argv [ 0 ] == " w " or self . argv [ 0 ] == " e " :
# check that cursor is in first argument
if cursor [ 0 ] == 1 :
# complete filesystem path
2021-10-16 20:19:22 +00:00
self . append_text_at_cursor ( self . complete_path ( self . argv [ cursor [ 0 ] ] , cursor [ 1 ] ) )
2021-10-16 06:09:53 +00:00
# complete filesystem path
def complete_path ( self , base , end ) :
paths = glob . glob ( glob . escape ( base [ : end ] ) + " * " )
print ( glob . escape ( base [ : end ] ) + " * " , paths )
if len ( paths ) == 0 :
return " "
elif len ( paths ) == 1 :
# append '/' to directories
if os . path . isdir ( paths [ 0 ] ) :
return paths [ 0 ] [ end : ] + " / "
else :
return paths [ 0 ] [ end : ]
else :
# display in status bar
2021-10-16 20:48:42 +00:00
self . app . status_bar . message = " "
2021-10-16 06:09:53 +00:00
for path in paths :
2021-10-16 16:29:39 +00:00
name = os . path . basename ( path )
# add quotes if needed
if " " in name :
name = " \" " + name + " \" "
2021-10-16 20:48:42 +00:00
self . app . status_bar . message + = name + " "
2021-10-16 06:46:06 +00:00
self . app . status_bar . draw ( )
2021-10-16 17:35:01 +00:00
return os . path . commonprefix ( paths ) [ end : ]
2021-10-16 20:19:22 +00:00
# run command
def run_command ( self ) :
# parse command line
self . parse_argv ( )
2021-12-01 19:23:21 +00:00
# commands that cannot be compounded
if self . argv [ 0 ] == " set " :
self . run_set ( self . argv )
return
2021-12-01 19:05:19 +00:00
# single letter commands (which can be combined)
2021-10-16 20:19:22 +00:00
# write
if self . argv [ 0 ] == " w " or self . argv [ 0 ] == " w! " or self . argv [ 0 ] == " wq " :
2021-12-01 19:05:19 +00:00
self . run_write ( self . argv )
2021-10-18 22:57:35 +00:00
# edit file
2021-10-16 20:19:22 +00:00
if self . argv [ 0 ] == " e " :
2021-12-01 19:05:19 +00:00
self . run_edit ( self . argv )
# quit
if self . argv [ 0 ] == " q " or self . argv == " wq " :
self . app . stop ( )
2021-12-01 19:23:21 +00:00
# set properties of particles
def run_set ( self , argv ) :
if len ( argv ) < 3 :
self . message = " error: ' set ' command was run with too few arguments -- usage: ' set <property> <value> ' "
return
if argv [ 1 ] == " color " :
self . run_set_color ( argv )
return
else :
self . message = " error: unrecognized command ' " + argv [ 1 ] + " ' "
return
def run_set_color ( self , argv ) :
if argv [ 2 ] == " blue " :
self . app . painter . set_color ( ( 0 , 0 , 1 ) )
elif argv [ 2 ] == " green " :
self . app . painter . set_color ( ( 0 , 1 , 0 ) )
elif argv [ 2 ] == " red " :
self . app . painter . set_color ( ( 1 , 0 , 0 ) )
elif argv [ 2 ] == " cyan " :
self . app . painter . set_color ( ( 0 , 1 , 1 ) )
elif argv [ 2 ] == " magenta " :
self . app . painter . set_color ( ( 1 , 0 , 1 ) )
elif argv [ 2 ] == " yellow " :
self . app . painter . set_color ( ( 1 , 1 , 0 ) )
elif argv [ 2 ] == " white " :
self . app . painter . set_color ( ( 1 , 1 , 1 ) )
elif argv [ 2 ] == " black " :
self . app . painter . set_color ( ( 0 , 0 , 0 ) )
elif argv [ 2 ] == " gray " :
self . app . painter . set_color ( ( 0.5 , 0.5 , 0.5 ) )
else :
color_str = argv [ 2 ] . split ( " , " )
# error if improperly formatted
if len ( color_str ) != 3 :
self . message = " error: unrecognized color specification ' " + argv [ 2 ] + " ' ; supported format is ' r,g,b ' or one of red|green|blue|cyan|magenta|yellow|white|black|gray "
return
try :
color = ( float ( color_str [ 0 ] ) , float ( color_str [ 1 ] ) , float ( color_str [ 2 ] ) )
except :
self . message = " error: unrecognized color specification ' " + argv [ 2 ] + " ' ; supported format is ' r,g,b ' or one of red|green|blue|cyan|magenta|yellow|white|black|gray "
return
self . app . painter . set_color ( color )
# write to file
2021-12-01 19:05:19 +00:00
def run_write ( self , argv ) :
if len ( argv ) > 2 :
self . message = " error: could not write to file: too many arguments -- usage: ' :w [path to file] ' "
return
elif len ( argv ) == 1 :
if self . app . openfile != " " :
if self . app . readonly :
self . message = " error: open file is readonly "
return
self . app . painter . write ( self . app . openfile )
2021-10-16 20:19:22 +00:00
return
2021-12-01 19:05:19 +00:00
else :
self . message = " error: no file is open for editing, specify a path "
2021-10-16 20:19:22 +00:00
return
2021-10-18 22:57:35 +00:00
2021-12-01 19:05:19 +00:00
( ret , self . message ) = filecheck . check_write ( argv [ 1 ] , argv [ 0 ] == " w! " )
# add comment if no overwrite
if ret == - 2 :
self . message + = " (use ' :w! ' to overwrite) "
if ret < 0 :
return
2021-10-20 04:55:20 +00:00
2021-12-01 19:05:19 +00:00
self . app . openfile = argv [ 1 ]
self . app . readonly = False
self . app . painter . write ( argv [ 1 ] )
# update status bar
self . app . status_bar . draw ( )
2021-10-18 22:57:35 +00:00
2021-12-01 19:23:21 +00:00
# edit file
2021-12-01 19:05:19 +00:00
def run_edit ( self , argv ) :
if len ( argv ) > 2 :
self . message = " error: could not open file: too many arguments -- usage: ' :e <path to file> ' "
return
elif len ( argv ) == 1 :
self . message = " error: could not open file: no argument found -- usage: ' :e <path to file> ' "
return
2021-10-16 20:19:22 +00:00
2021-12-01 19:05:19 +00:00
# check that the file can be edited
( ret , self . message ) = filecheck . check_edit ( argv [ 1 ] )
# error
if ret < 0 :
return
if os . path . isfile ( argv [ 1 ] ) :
# read the file
self . app . painter . read ( argv [ 1 ] )
# set readonly mode
self . app . readonly = not os . access ( argv [ 1 ] , os . W_OK )
else :
# new file, reset painter
self . app . painter . reset ( )
self . app . readonly = False
# select openfile in app
self . app . openfile = argv [ 1 ]
# update status bar
self . app . status_bar . draw ( )