Finishes splitting up the editor into a more modular code style
This commit is contained in:
parent
a68f3ebd15
commit
2c28ed8eab
371
chalk
371
chalk
|
@ -7,12 +7,29 @@
|
|||
# provisions
|
||||
#_
|
||||
|
||||
###########################################################
|
||||
# Imports
|
||||
###########################################################
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
import readline
|
||||
|
||||
###########################################################
|
||||
# Globals
|
||||
###########################################################
|
||||
|
||||
content = []
|
||||
file_changed = False
|
||||
filepath = ''
|
||||
filename = ''
|
||||
|
||||
|
||||
###########################################################
|
||||
# Classes
|
||||
###########################################################
|
||||
|
||||
class c:
|
||||
black = ''
|
||||
|
@ -30,6 +47,13 @@ class c:
|
|||
end = '\033[0m'
|
||||
|
||||
|
||||
###########################################################
|
||||
# Functions
|
||||
###########################################################
|
||||
|
||||
|
||||
### Utilities and helpers #################################
|
||||
|
||||
def input_editable(prompt, prefill=''):
|
||||
readline.set_startup_hook(lambda: readline.insert_text(prefill))
|
||||
try:
|
||||
|
@ -47,6 +71,7 @@ def validate_path(path):
|
|||
print("Invalid filepath")
|
||||
os.exit(2)
|
||||
|
||||
|
||||
def print_help():
|
||||
helptext = [
|
||||
"",
|
||||
|
@ -57,17 +82,25 @@ def print_help():
|
|||
" {}!d{} - Display the contents of the file".format(c.b_green, c.end),
|
||||
" {}!v{} - View lines, prompt will request a range of lines".format(c.b_green, c.end),
|
||||
" {}!#{} - Rewrite a line, # is replaced by line number e.g. !27".format(c.b_green, c.end),
|
||||
" {}!x{} - Delete line(s), prompt will request line or range".format(c.b_green, c.end),
|
||||
" {}!i{} - Insert line(s), prompt will request start point and optional quantity".format(c.b_green, c.end),
|
||||
" {}!x{} - Delete line(s), prompt will request line range".format(c.b_green, c.end),
|
||||
" {}!i{} - Insert line(s), prompt will request start point and quantity".format(c.b_green, c.end),
|
||||
" {}!g{} - Print the ruler/guide".format(c.b_green, c.end),
|
||||
" {}!c{} - Copy line(s) to the paste buffer, prompt will request line range".format(c.b_green, c.end), # TODO
|
||||
" {}!p{} - Paste the contents of the paste buffer, prompt will request location".format(c.b_green, c.end), # TODO
|
||||
" {}!b{} - Buffer view (print the paste buffer to the screen)".format(c.b_green, c.end), # TODO
|
||||
"",
|
||||
""
|
||||
]
|
||||
for x in helptext:
|
||||
print('{:8} {}'.format(' ',x))
|
||||
|
||||
# Print ruler will print the text guide/rule
|
||||
# at the width of the current terminal - ui indent
|
||||
def print_ruler():
|
||||
width = os.get_terminal_size()[0] - 9
|
||||
try:
|
||||
width = os.get_terminal_size()[0] - 9
|
||||
except:
|
||||
width = 60 - 9
|
||||
counter = " "
|
||||
ticker = " "
|
||||
current = 5
|
||||
|
@ -78,26 +111,73 @@ def print_ruler():
|
|||
print(counter)
|
||||
print(ticker)
|
||||
|
||||
|
||||
# Print banner will print the program name,
|
||||
def print_banner(fn):
|
||||
print('\n Chalk 1.0 by sloum')
|
||||
print('\n{} Writing:{} {}{}'.format(c.yellow, c.white, fn, c.end))
|
||||
print(" For a command list, enter {}!?\n{}".format(c.green, c.end))
|
||||
|
||||
|
||||
def chalk(path):
|
||||
abspath = os.path.abspath(path)
|
||||
# Build contents from file sets the absolute
|
||||
# file path, the file name, and loads in any
|
||||
# content contained in a file
|
||||
def build_contents_from_file(path):
|
||||
global content
|
||||
global filepath
|
||||
global filename
|
||||
filepath = os.path.abspath(path)
|
||||
filename = path.split('/')[-1]
|
||||
try:
|
||||
validate_path(abspath)
|
||||
with open(os.path.abspath(abspath), 'r') as f:
|
||||
validate_path(filepath)
|
||||
with open(os.path.abspath(filepath), 'r') as f:
|
||||
content = f.read().split('\n')
|
||||
if content[-1] == '':
|
||||
content.pop()
|
||||
except FileNotFoundError:
|
||||
content = []
|
||||
|
||||
filename = path.split('/')[-1]
|
||||
|
||||
edited_file = False
|
||||
# Save changes saves changes to a file
|
||||
def save_changes():
|
||||
global file_changed
|
||||
|
||||
if not file_changed:
|
||||
return True
|
||||
|
||||
text = '\n'.join(content)
|
||||
text += '\n'
|
||||
try:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(text)
|
||||
print('Saved \033[1m{}\033[0m'.format(filename))
|
||||
file_changed = False
|
||||
return True
|
||||
except PermissionError:
|
||||
print('{} You do not have permission to write to this file.{}'.format(c.red, c.end))
|
||||
return False
|
||||
|
||||
|
||||
# Yes No queries the user with a yes no question and returns
|
||||
# True on yes and False on no
|
||||
def yes_no(question):
|
||||
confirmation = ''
|
||||
while confirmation not in ['y','yes','n','no']:
|
||||
confirmation = input(question)
|
||||
confirmation = confirmation.lower()
|
||||
return True if confirmation in ['y', 'yes'] else False
|
||||
|
||||
|
||||
|
||||
##### Input and Command Routers ###########################
|
||||
|
||||
# Chalk is the entry point into the editor, contains the
|
||||
# input loop and does some routing
|
||||
def chalk(path):
|
||||
global filename
|
||||
global file_changed
|
||||
|
||||
build_contents_from_file(path)
|
||||
|
||||
print_banner(filename)
|
||||
print_ruler()
|
||||
|
@ -105,127 +185,194 @@ def chalk(path):
|
|||
while True:
|
||||
ln = input('{:6} {}>{} '.format(len(content), c.yellow, c.end))
|
||||
if ln == '.':
|
||||
break
|
||||
elif ln == '!?':
|
||||
print_help()
|
||||
elif ln == '!g':
|
||||
print_ruler()
|
||||
elif ln == '!d':
|
||||
print('\n - - -')
|
||||
for i, x in enumerate(content):
|
||||
print('{:6} - {}{}{}'.format(i, c.green, x, c.end))
|
||||
print(' - - -\n')
|
||||
elif ln == '!x':
|
||||
print('\n{:8} {}Enter line to delete, for a range enter the start and\n{:8} end separated by a space. To cancel, enter -1.\n{:8} Deletion cannot be undone. Be careful.{}'.format(' ', c.cyan, ' ', ' ', c.end))
|
||||
delete = input('{:6} {}>{} '.format(' ', c.b_red, c.end))
|
||||
entry = delete.split(' ')
|
||||
try:
|
||||
beg = int(entry[0])
|
||||
end = beg + 1
|
||||
if len(entry) > 1:
|
||||
end = int(entry[1]) + 1
|
||||
if beg < 0:
|
||||
continue
|
||||
content = content[:beg] + content[end:]
|
||||
edited_file = True
|
||||
except:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
print('')
|
||||
elif ln == '!i':
|
||||
print('\n{:8} {}Enter the line number that you want the new line(s) to\n{:8} appear after.To insert multiple lines enter the starting\n{:8} point and the number of lines separated by a space.\n{:8}To cancel, enter -1{}'.format(' ', c.cyan,' ', ' ', ' ', c.end))
|
||||
insert = input('{:6} {}>{} '.format(' ', c.b_green, c.end))
|
||||
entry = insert.split(' ')
|
||||
try:
|
||||
beg = int(entry[0]) + 1
|
||||
count = 1
|
||||
if len(entry) > 1:
|
||||
count = int(entry[1])
|
||||
if beg < 0 or count < 1:
|
||||
continue
|
||||
while count > 0:
|
||||
content.insert(beg,'')
|
||||
count -= 1
|
||||
edited_file = True
|
||||
except:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
print('')
|
||||
elif ln == '!v':
|
||||
print('\n{:8} {}Enter the start & end, separated by a space, to indicate\n{:8} the range you wish to view. To cancel, enter: -1{}'.format(' ', c.cyan, ' ', c.end))
|
||||
view = input('{:6} {}>{}'.format(' ', c.yellow, c.end))
|
||||
entry = view.split(' ')
|
||||
try:
|
||||
beg = int(entry[0])
|
||||
if beg < 0:
|
||||
continue
|
||||
|
||||
if len(entry) < 2 or beg > int(entry[1]):
|
||||
print('{}{:8} Invalid entry, must provide two numbers separated by a space{}'.format(c.red, ' ', c.end))
|
||||
continue
|
||||
end = int(entry[1])
|
||||
end = end if end < len(content) else len(content) - 1
|
||||
if beg > end:
|
||||
print('{}{:8} Invalid entry, start position must be less than end position.{}'.format(c.red, ' ', c.end))
|
||||
continue
|
||||
else:
|
||||
for x in range(beg, end + 1):
|
||||
print('{:6} - {}{}{}'.format(x, c.green, content[x], c.end))
|
||||
print('')
|
||||
print_ruler()
|
||||
|
||||
except:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
# End the editing session (quit)
|
||||
# Will query for save if the file has been changed
|
||||
quit()
|
||||
elif len(ln) == 2 and ln[0] == '!':
|
||||
# Route a command
|
||||
command_router(ln)
|
||||
elif re.match(r'^\!\d+$',ln):
|
||||
try:
|
||||
row = int(ln[1:])
|
||||
newln = input_editable('{:6} {}>{} '.format(row, c.b_blue, c.end), content[row])
|
||||
if newln == '!c':
|
||||
print('{:8} Cancelled...'.format(' '))
|
||||
else:
|
||||
content[row] = newln
|
||||
edited_file = True
|
||||
except:
|
||||
print('{}{:8} Invalid entry!{}'.format(c.b_red, ' ', c.end))
|
||||
print('')
|
||||
print_ruler()
|
||||
# Edit a previous line
|
||||
edit_line(ln)
|
||||
else:
|
||||
edited_file = True
|
||||
# Add new content
|
||||
content.append(ln)
|
||||
edited_file = True
|
||||
file_changed = True
|
||||
|
||||
if not edited_file:
|
||||
# Command router takes a command line and routes it to its
|
||||
# command function
|
||||
def command_router(ln):
|
||||
if ln == '!?':
|
||||
print_help()
|
||||
elif ln == '!g':
|
||||
print_ruler()
|
||||
elif ln == '!d':
|
||||
display_file()
|
||||
elif ln == '!x':
|
||||
delete_lines()
|
||||
elif ln == '!i':
|
||||
insert_lines()
|
||||
elif ln == '!v':
|
||||
view_rows()
|
||||
else:
|
||||
print('{:9}{}Unknown command: {}{}'.format(' ', c.red, ln, c.end))
|
||||
|
||||
|
||||
|
||||
##### Command Functions ###################################
|
||||
|
||||
# Edit line edits an existing line or prints an error
|
||||
# if the line was unable to be edited
|
||||
def edit_line(ln):
|
||||
global content
|
||||
global file_changed
|
||||
try:
|
||||
row = int(ln[1:])
|
||||
newln = input_editable('{:6} {}>{} '.format(row, c.b_blue, c.end), content[row])
|
||||
if newln == '!c':
|
||||
print('{:8} Cancelled...'.format(' '))
|
||||
else:
|
||||
content[row] = newln
|
||||
file_changed = True
|
||||
except:
|
||||
print('{}{:8} Invalid entry!{}'.format(c.b_red, ' ', c.end))
|
||||
|
||||
|
||||
# Quit will quit the program, but first will ask to save if there
|
||||
# are any unsaved changes
|
||||
def quit():
|
||||
if not file_changed:
|
||||
print('{}exiting...{}\n'.format(c.white, c.end))
|
||||
sys.exit(0)
|
||||
save = yes_no('{}Save changes to {}?{} (Y/n) '.format(c.b_green, filename, c.end))
|
||||
if save:
|
||||
saved = save_changes()
|
||||
if saved:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('{}exiting...{}\n'.format(c.white, c.end))
|
||||
sys.exit(0)
|
||||
|
||||
confirmation = ''
|
||||
while confirmation.lower() not in ['y','yes','n','no']:
|
||||
confirmation = input('{}Save {}?{} (Y/n) '.format(c.b_green, filename, c.end))
|
||||
|
||||
if not len(confirmation):
|
||||
continue
|
||||
# Display file prints the whole file, line by line, to
|
||||
# stdout
|
||||
def display_file():
|
||||
print('\n - - -')
|
||||
for i, x in enumerate(content):
|
||||
print('{:6} - {}{}{}'.format(i, c.green, x, c.end))
|
||||
print(' - - -\n')
|
||||
|
||||
if confirmation.lower()[0] == 'y':
|
||||
print('{}saving...{}'.format(c.white, c.end))
|
||||
text = '\n'.join(content)
|
||||
text += '\n'
|
||||
try:
|
||||
with open(abspath, 'w') as f:
|
||||
f.write(text)
|
||||
print('Saved \033[1m{}\033[0m'.format(abspath))
|
||||
sys.exit(0)
|
||||
except PermissionError:
|
||||
print('{} You do not have permission to write to this file.{}'.format(c.red, c.end))
|
||||
sys.exit(1)
|
||||
|
||||
def delete_lines():
|
||||
global content
|
||||
global file_changed
|
||||
|
||||
print('{:9}{}Enter the line number you want to start deleting at:{}'.format(' ', c.cyan, c.end))
|
||||
beg = input('{:6} {}>{} '.format(' ', c.b_red, c.end))
|
||||
|
||||
print('{:9}{}Enter the last line number you want to delete (or $ for end of file){}:'.format(' ', c.cyan, c.end))
|
||||
end = input('{:6} {}>{} '.format(' ', c.b_red, c.end))
|
||||
|
||||
continue_delete = yes_no('{:9}{}Are you sure you want to delete lines {} - {}? (y/n){} '.format(' ', c.cyan, beg, end, c.end))
|
||||
if not continue_delete:
|
||||
print('{:9}Deletion canceled.'.format(' '))
|
||||
|
||||
if end == '$':
|
||||
end = len(content) - 1
|
||||
try:
|
||||
beg = int(beg)
|
||||
end = int(end) + 1
|
||||
if beg < 0 or beg > end:
|
||||
print('{}{:9}Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
return
|
||||
|
||||
# TODO: Add content to copy buffer
|
||||
|
||||
if end == len(content):
|
||||
content = content[:beg]
|
||||
else:
|
||||
print('{}exiting...{}\n'.format(c.white, c.end))
|
||||
sys.exit(0)
|
||||
content = content[:beg] + content[end:]
|
||||
file_changed = True
|
||||
except:
|
||||
print('{}{:9}Invalid entry{}\n'.format(c.red, ' ', c.end))
|
||||
|
||||
|
||||
def insert_lines():
|
||||
global content
|
||||
global file_changed
|
||||
|
||||
print('{:9}{}Enter the line number you want to insert lines before:{}'.format(' ', c.cyan, c.end))
|
||||
beg = input('{:6} {}>{} '.format(' ', c.b_green, c.end))
|
||||
|
||||
print('{:9}{}Enter the number of rows you want to insert{}:'.format(' ', c.cyan, c.end))
|
||||
count = input('{:6} {}>{} '.format(' ', c.b_green, c.end))
|
||||
|
||||
continue_insert = yes_no('{:9}{}Are you sure you want to insert {} rows before line {}? (y/n){} '.format(' ', c.cyan, count, beg, c.end))
|
||||
if not continue_insert:
|
||||
print('{:9}Insertion canceled.'.format(' '))
|
||||
|
||||
|
||||
print('\n{:8} {}Enter the line number that you want the new line(s) to\n{:8} appear after.To insert multiple lines enter the starting\n{:8} point and the number of lines separated by a space.\n{:8}To cancel, enter -1{}'.format(' ', c.cyan,' ', ' ', ' ', c.end))
|
||||
try:
|
||||
beg = int(beg)
|
||||
count = int(count)
|
||||
|
||||
if beg < 0 or beg > len(content) or count < 1:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
return
|
||||
|
||||
while count > 0:
|
||||
content.insert(beg,'')
|
||||
count -= 1
|
||||
file_changed = True
|
||||
except:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
|
||||
|
||||
def view_rows():
|
||||
print('{:9}{}Enter the line number you want to start viewing from:{}'.format(' ', c.cyan, c.end))
|
||||
start = input('{:6} {}>{} '.format(' ', c.yellow, c.end))
|
||||
|
||||
print('{:9}{}Enter the last line you want to view ($ for end of file):{}'.format(' ', c.cyan, c.end))
|
||||
finish = input('{:6} {}>{} '.format(' ', c.yellow, c.end))
|
||||
if finish == '$':
|
||||
finish = len(content) - 1
|
||||
|
||||
try:
|
||||
beg = int(start)
|
||||
end = int(finish)
|
||||
|
||||
if beg > end or beg < 0 or end > len(content) - 1:
|
||||
print('{}{:9}Invalid entry x{}'.format(c.red, ' ', c.end))
|
||||
return
|
||||
else:
|
||||
print('')
|
||||
for num, ln in enumerate(content[beg:end+1]):
|
||||
print('{:6} - {}{}{}'.format(num+beg, c.green, ln, c.end))
|
||||
print('')
|
||||
|
||||
except:
|
||||
print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end))
|
||||
|
||||
|
||||
|
||||
###########################################################
|
||||
# Init
|
||||
###########################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = sys.argv
|
||||
if len(args) < 2:
|
||||
print('Incorrect number of arguments.')
|
||||
sys.exit(1)
|
||||
filepath = args[1]
|
||||
|
||||
# Set readline settings
|
||||
readline.parse_and_bind('set editing-mode emacs')
|
||||
readline.parse_and_bind('set show-mode-in-prompt off')
|
||||
|
||||
# Run the editor
|
||||
chalk(filepath)
|
||||
|
||||
|
|
Loading…
Reference in New Issue