From 38b9f23d3680adff54bbf23075640140645c63ba Mon Sep 17 00:00:00 2001 From: sloum Date: Sun, 19 Apr 2020 11:11:11 -0700 Subject: [PATCH] Added new features for copying and pasting --- adf | 11 +++ chalk | 311 +++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 230 insertions(+), 92 deletions(-) create mode 100644 adf diff --git a/adf b/adf new file mode 100644 index 0000000..eee53a7 --- /dev/null +++ b/adf @@ -0,0 +1,11 @@ +This is +a test +to see +how well +things will +This is +a test +work when +I start +using the +new features diff --git a/chalk b/chalk index c5a01cf..8647c10 100755 --- a/chalk +++ b/chalk @@ -22,6 +22,7 @@ import readline ########################################################### content = [] +paste_buffer = [] file_changed = False filepath = '' filename = '' @@ -45,13 +46,14 @@ class c: b_blue = '\033[1;34m' white = '\033[1;37m' end = '\033[0m' + bold = '\033[1m' ########################################################### # Functions ########################################################### - +### ################################# ### Utilities and helpers ################################# def input_editable(prompt, prefill=''): @@ -68,27 +70,44 @@ def validate_path(path): path_body = '/'.join(path_body_list) is_path = os.path.isdir(path_body) if not is_path: - print("Invalid filepath") - os.exit(2) + return False + return True + +def check_file_writable(fnm): + if os.path.exists(fnm): + if os.path.isfile(fnm): # is it a file or a dir? + return os.access(fnm, os.W_OK) + else: + return False + pdir = os.path.dirname(fnm) + if not pdir: pdir = '.' + return os.access(pdir, os.W_OK) def print_help(): helptext = [ - "", "", "{}Commands are entered as the only entry for their row:{}".format(c.yellow, c.end), - " {}.{} - Finish writing/exit, will prompt for save".format(c.b_green, c.end), - " {}!?{} - Print this help message".format(c.b_green, c.end), - " {}!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 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 "", + " {}!?{} - Print this help message".format(c.b_green, c.end), + " {}!g{} - Print the ruler/guide".format(c.b_green, c.end), + "", + " {}!d{} - Display the whole file".format(c.b_green, c.end), + " {}!v{} - View range of lines (will request line range)".format(c.b_green, c.end), + "", + " {}!#{} - Edit a line (i.e. !27)".format(c.b_green, c.end), + " {}!i{} - Insert empty line(s) (will request location/count)".format(c.b_green, c.end), + " {}!x{} - Cut/copy line(s) (will request line range)".format(c.b_green, c.end), + "", + " {}!c{} - Copy to the paste buffer (will request line range)".format(c.b_green, c.end), # TODO + " {}!p{} - Paste from the paste buffer (will request destination)".format(c.b_green, c.end), # TODO + " {}!b{} - Buffer view (print the paste buffer)".format(c.b_green, c.end), # TODO + "", + " {}!s{} - Save changes to the document".format(c.b_green, c.end), + " {}!a{} - Save as a new file (will request file location)".format(c.b_green, c.end), + " {}.{} - Finish writing/exit (will prompt for save)".format(c.b_green, c.end), + "", + "{}- - -{}".format(c.yellow, c.end), "" ] for x in helptext: @@ -129,13 +148,110 @@ def build_contents_from_file(path): filepath = os.path.abspath(path) filename = path.split('/')[-1] try: - validate_path(filepath) + valid_path = validate_path(filepath) + if not valid_path: + print('Invalid file path') + os.exit(2) + with open(os.path.abspath(filepath), 'r') as f: content = f.read().split('\n') if content[-1] == '': content.pop() except FileNotFoundError: content = [] + except PermissionError: + print('You do not have permission to read {}'.format(path)) + os.exit(2) + + +# 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() + + while True: + ln = input('{:6} {}>{} '.format(len(content), c.yellow, c.end)) + if ln == '.': + # End the editing session (quit) + # Will query for save if the file has been changed + quit() + elif re.match(r'^\!\d+$',ln): + # Edit a previous line + edit_line(ln) + elif len(ln) == 2 and ln[0] == '!': + # Route a command + command_router(ln) + else: + # Add new content + content.append(ln) + file_changed = True + +# 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 == '!v': + view_rows() + elif ln == '!i': + insert_lines() + elif ln == '!x': + cut_lines() + elif ln == '!c': + copy_rows() + elif ln == '!p': + paste_from_buffer() + elif ln == '!b': + view_paste_buffer() + elif ln == '!s': + save_changes() + elif ln == '!a': + save_as() + 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)) # Save changes saves changes to a file @@ -156,88 +272,39 @@ def save_changes(): except PermissionError: print('{} You do not have permission to write to this file.{}'.format(c.red, c.end)) return False + except: + print('{} Error while writing to file: {}{}'.format(c.red, e, 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): +# Save as will switch the save location to +# a new file path and save the content buffer +# to that new path. It will validate the path +# beforehand +def save_as(): + global filepath global filename global file_changed - build_contents_from_file(path) + print('{:9}{}Enter the new save path (can be relative):{}'.format(' ', c.cyan, c.end)) + path = input('{:6} {}>{} '.format(' ', c.green, c.end)) - print_banner(filename) - print_ruler() + continue_save_as = yes_no('{:9}{}Are you sure you want to save as {}? (y/n){} '.format(' ', c.cyan, path, c.end)) + if not continue_save_as: + print('{:9}Save canceled.'.format(' ')) - while True: - ln = input('{:6} {}>{} '.format(len(content), c.yellow, c.end)) - if ln == '.': - # 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): - # Edit a previous line - edit_line(ln) - else: - # Add new content - content.append(ln) - file_changed = True + fp = os.path.abspath(path) + fn = path.split('/')[-1] + valid = check_file_writable(fp) + if not valid: + print('{:9}{}The path is invalid, save cancelled{}'.format(' ', c.red, c.end)) -# 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)) + filepath = fp + filename = fn + file_changed = True + save_changes() - -##### 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(): @@ -265,9 +332,10 @@ def display_file(): print(' - - -\n') -def delete_lines(): +def cut_lines(): global content global file_changed + global paste_buffer 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)) @@ -288,7 +356,7 @@ def delete_lines(): print('{}{:9}Invalid entry{}'.format(c.red, ' ', c.end)) return - # TODO: Add content to copy buffer + paste_buffer = content[beg:end] if end == len(content): content = content[:beg] @@ -313,8 +381,6 @@ def insert_lines(): 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) @@ -357,6 +423,67 @@ def view_rows(): print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end)) +def copy_rows(): + global paste_buffer + + print('{:9}{}Enter the line number you want to start copying from:{}'.format(' ', c.cyan, c.end)) + start = input('{:6} {}>{} '.format(' ', c.yellow, c.end)) + + print('{:9}{}Enter the last line you want to copy ($ 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: + paste_buffer = content[beg:end + 1] + + except: + print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end)) + + +def paste_from_buffer(): + global content + global file_changed + + print('{:9}{}Enter a line number. The pasted data will be inserted {}before{} the given line:{}'.format(' ', c.cyan, '\033[4m', '\033[24m', c.end)) + beg = input('{:6} {}>{} '.format(' ', c.b_green, c.end)) + + continue_paste = yes_no('{:9}{}Are you sure you want to paste from the paste buffer before line {}? (y/n){} '.format(' ', c.cyan, beg, c.end)) + if not continue_paste: + print('{:9}Paste canceled.'.format(' ')) + + try: + beg = int(beg) + + if beg < 0 or beg > len(content): + print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end)) + return + + for row in paste_buffer[::-1]: + content.insert(beg,row) + file_changed = True + except: + print('{}{:8} Invalid entry{}'.format(c.red, ' ', c.end)) + + +def view_paste_buffer(): + print('') + if len(paste_buffer): + for num, ln in enumerate(paste_buffer): + print('{:6} - {}{}{}'.format('pb', c.blue, ln, c.end)) + else: + print('{:6} - {}{}{}'.format('pb', c.blue, 'The paste buffer is currently empty', c.end)) + print('') + + + ########################################################### # Init