def check_overflow(self, line): """ Update the known location of a shown history to account for the possibility of overflowing the display buffer. """ (buf_width, buf_height) = get_buffer_size() (cur_x, cur_y) = get_cursor() lines_written = len(line) / buf_width + 1 if cur_y + lines_written > buf_height: self.offset_from_bottom += cur_y + lines_written - buf_height
def check_overflow(self, line): """ Update the known location of a shown history to account for the possibility of overflowing the display buffer. """ (buf_width, buf_height) = get_buffer_size() (cur_x, cur_y) = get_cursor() lines_written = len(line) // buf_width + 1 if cur_y + lines_written > buf_height: self.offset_from_bottom += cur_y + lines_written - buf_height
def display(self): """ Nicely formatted display of the location history, with current location highlighted. If a clean display is present on the screen, this overwrites it to perform an 'update'. """ buffer_size = get_buffer_size() if self.shown and self.disp_size == buffer_size: # We just need to update the previous display, so we # go back to the original display start point move_cursor(0, buffer_size[1] - self.offset_from_bottom) else: # We need to redisplay, so remember the start point for # future updates self.disp_size = buffer_size self.offset_from_bottom = buffer_size[1] - get_cursor()[1] stdout.write('\n') lines_written = 2 stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT) for i in range(len(self.locations)): location = self.locations[i] if i < 9: prefix = ' +%d ' % (i + 1) else: prefix = ' %d ' % (i + 1) lines_written += (len(prefix + location) / buffer_size[0] + 1) if i != self.index: # Non-selected entry, simply print stdout.write(prefix + location + '\n') else: # Currently selected entry, print with highlight stdout.write(appearance.colors.dir_history_selection + prefix + location + color.Fore.DEFAULT + color.Back.DEFAULT) stdout.write(' ' * (buffer_size[0] - get_cursor()[0])) # Check whether we have overflown the buffer if lines_written > self.offset_from_bottom: self.offset_from_bottom = lines_written # Mark a clean display of the history self.shown = True
def display(self): """ Nicely formatted display of the location history, with current location highlighted. If a clean display is present on the screen, this overwrites it to perform an 'update'. """ buffer_size = get_buffer_size() if self.shown and self.disp_size == buffer_size: # We just need to update the previous display, so we # go back to the original display start point move_cursor(0, buffer_size[1] - self.offset_from_bottom) else: # We need to redisplay, so remember the start point for # future updates self.disp_size = buffer_size self.offset_from_bottom = buffer_size[1] - get_cursor()[1] stdout.write('\n') lines_written = 2 stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT) for i in range(len(self.locations)): location = self.locations[i] prefix = ' %d ' % (i + 1) lines_written += (len(prefix + location) / buffer_size[0] + 1) if i != self.index: # Non-selected entry, simply print stdout.write(prefix + location + '\n') else: # Currently selected entry, print with highlight stdout.write(appearance.colors.dir_history_selection + prefix + location + color.Fore.DEFAULT + color.Back.DEFAULT) stdout.write(' ' * (buffer_size[0] - get_cursor()[0])) # Check whether we have overflown the buffer if lines_written > self.offset_from_bottom: self.offset_from_bottom = lines_written # Mark a clean display of the history self.shown = True
def main(): title_prefix = "" # Apply global and user configurations apply_settings(pycmd_install_dir + '\\init.py', (pycmd_install_dir, pycmd_data_dir)) apply_settings(pycmd_data_dir + '\\init.py', (pycmd_install_dir, pycmd_data_dir)) sanitize_settings() init_hooks = get_hooks(hook_types[0]) if len(init_hooks) > 0: for hook in init_hooks.values(): hook() # Parse arguments arg = 1 while arg < len(sys.argv): switch = sys.argv[arg].upper() rest = ['"' + t + '"' if os.path.exists(t) else t for t in sys.argv[arg+1:]] if switch in ['/K', '-K']: # Run the specified command and continue if len(rest) > 0: run_command(rest) dir_hist.visit_cwd() break elif switch in ['/C', '-C']: # Run the specified command end exit if len(rest) > 0: run_command(rest) internal_exit() elif switch in ['/H', '/?', '-H']: # Show usage information and exit print_usage() internal_exit() elif switch in ['/T', '-T']: if arg == len(sys.argv) - 1: sys.stderr.write('PyCmd: no title specified to \'-t\'\n') print_usage() internal_exit() title_prefix = sys.argv[arg + 1] + ' - ' arg += 1 elif switch in ['/I', '-I']: if arg == len(sys.argv) - 1: sys.stderr.write('PyCmd: no script specified to \'-i\'\n') print_usage() internal_exit() apply_settings(sys.argv[arg + 1], (pycmd_install_dir, pycmd_data_dir)) sanitize_settings() arg += 1 elif switch in ['/Q', '-Q']: # Quiet mode: suppress messages behavior.quiet_mode = True else: # Invalid command line switch sys.stderr.write('PyCmd: unrecognized option `' + sys.argv[arg] + '\'\n') print_usage() internal_exit() arg += 1 if not behavior.quiet_mode: # Print some splash text try: from buildinfo import build_info except ImportError: build_info = '<no build info>' print print 'Welcome to PyCmd %s!' % build_info print # Run an empty command to initialize environment run_command(['echo', '>', 'NUL']) # Main loop while True: # Prepare buffer for reading one line state.reset_line(appearance.prompt()) scrolling = False auto_select = False force_repaint = True dir_hist.shown = False print while True: # Update console title and environment curdir = os.getcwd() curdir = curdir[0].upper() + curdir[1:] console.set_console_title(title_prefix + curdir + ' - PyCmd') os.environ['CD'] = curdir if state.changed() or force_repaint: prev_total_len = len(remove_escape_sequences(state.prev_prompt) + state.prev_before_cursor + state.prev_after_cursor) set_cursor_visible(False) cursor_backward(len(remove_escape_sequences(state.prev_prompt) + state.prev_before_cursor)) sys.stdout.write('\r') # Update the offset of the directory history in case of overflow # Note that if the history display is marked as 'dirty' # (dir_hist.shown == False) the result of this action can be # ignored dir_hist.check_overflow(remove_escape_sequences(state.prompt)) # Write current line sys.stdout.write('\r' + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.prompt + state.prompt + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text) line = state.before_cursor + state.after_cursor if state.history.filter == '': sel_start, sel_end = state.get_selection_range() sys.stdout.write(line[:sel_start] + appearance.colors.selection + line[sel_start: sel_end] + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[sel_end:]) else: pos = 0 colored_line = '' for (start, end) in state.history.current()[1]: colored_line += color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[pos : start] colored_line += appearance.colors.search_filter + line[start : end] pos = end colored_line += color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[pos:] sys.stdout.write(colored_line) # Erase remaining chars from old line to_erase = prev_total_len - len(remove_escape_sequences(state.prompt) + state.before_cursor + state.after_cursor) if to_erase > 0: sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * to_erase) cursor_backward(to_erase) # Move cursor to the correct position set_cursor_visible(True) cursor_backward(len(state.after_cursor)) # Prepare new input state state.step_line() # Read and process a keyboard event rec = read_input() select = auto_select or is_shift_pressed(rec) # Will be overriden if Shift-PgUp/Dn is pressed force_repaint = not is_control_only(rec) #print '\n\n', rec.keyDown, rec.char, rec.virtualKeyCode, rec.controlKeyState, '\n\n' recChar = rec.CU.Char if PYPY else rec.Char if is_ctrl_pressed(rec) and not is_alt_pressed(rec): # Ctrl-Something if recChar == chr(4): # Ctrl-D if state.before_cursor + state.after_cursor == '': internal_exit('\r\nBye!') else: state.handle(ActionCode.ACTION_DELETE) elif recChar == chr(31): # Ctrl-_ state.handle(ActionCode.ACTION_UNDO_EMACS) auto_select = False elif rec.VirtualKeyCode == 75: # Ctrl-K state.handle(ActionCode.ACTION_KILL_EOL) elif rec.VirtualKeyCode == 32: # Ctrl-Space auto_select = True state.reset_selection() elif rec.VirtualKeyCode == 71: # Ctrl-G if scrolling: scrolling = False else: state.handle(ActionCode.ACTION_ESCAPE) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) auto_select = False elif rec.VirtualKeyCode == 65: # Ctrl-A state.handle(ActionCode.ACTION_HOME, select) elif rec.VirtualKeyCode == 69: # Ctrl-E state.handle(ActionCode.ACTION_END, select) elif rec.VirtualKeyCode == 66: # Ctrl-B state.handle(ActionCode.ACTION_LEFT, select) elif rec.VirtualKeyCode == 70: # Ctrl-F state.handle(ActionCode.ACTION_RIGHT, select) elif rec.VirtualKeyCode == 80: # Ctrl-P state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 78: # Ctrl-N state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 37: # Ctrl-Left state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 39: # Ctrl-Right state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 46: # Ctrl-Delete state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 67: # Ctrl-C # The Ctrl-C signal is caught by our custom handler, and a # synthetic keyboard event is created so that we can catch # it here if state.get_selection() != '': state.handle(ActionCode.ACTION_COPY) else: state.handle(ActionCode.ACTION_ESCAPE) auto_select = False elif rec.VirtualKeyCode == 88: # Ctrl-X state.handle(ActionCode.ACTION_CUT) auto_select = False elif rec.VirtualKeyCode == 87: # Ctrl-W state.handle(ActionCode.ACTION_CUT) auto_select = False elif rec.VirtualKeyCode == 86: # Ctrl-V state.handle(ActionCode.ACTION_PASTE) auto_select = False elif rec.VirtualKeyCode == 89: # Ctrl-Y state.handle(ActionCode.ACTION_PASTE) auto_select = False elif rec.VirtualKeyCode == 8: # Ctrl-Backspace state.handle(ActionCode.ACTION_BACKSPACE_WORD) elif rec.VirtualKeyCode == 90: if not is_shift_pressed(rec): # Ctrl-Z state.handle(ActionCode.ACTION_UNDO) else: # Ctrl-Shift-Z state.handle(ActionCode.ACTION_REDO) auto_select = False elif is_alt_pressed(rec) and not is_ctrl_pressed(rec): # Alt-Something if rec.VirtualKeyCode in [37, 39] + range(49, 59): # Dir history if state.before_cursor + state.after_cursor == '': state.reset_prev_line() if rec.VirtualKeyCode == 37: # Alt-Left changed = dir_hist.go_left() elif rec.VirtualKeyCode == 39: # Alt-Right changed = dir_hist.go_right() else: # Alt-1..Alt-9 changed = dir_hist.jump(rec.VirtualKeyCode - 48) if changed: state.prev_prompt = state.prompt state.prompt = appearance.prompt() save_history(dir_hist.locations, pycmd_data_dir + '\\dir_history', dir_hist.max_len) if dir_hist.shown: dir_hist.display() sys.stdout.write(state.prev_prompt) else: if rec.VirtualKeyCode == 37: # Alt-Left state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 39: # Alt-Right state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 66: # Alt-B state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 70: # Alt-F state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 80: # Alt-P state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 78: # Alt-N state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 68: # Alt-D if state.before_cursor + state.after_cursor == '': dir_hist.display() dir_hist.check_overflow(remove_escape_sequences(state.prev_prompt)) sys.stdout.write(state.prev_prompt) else: state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 87: # Alt-W state.handle(ActionCode.ACTION_COPY) state.reset_selection() auto_select = False elif rec.VirtualKeyCode == 46: # Alt-Delete state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 8: # Alt-Backspace state.handle(ActionCode.ACTION_BACKSPACE_WORD) elif rec.VirtualKeyCode == 191: state.handle(ActionCode.ACTION_EXPAND) elif is_shift_pressed(rec) and rec.VirtualKeyCode == 33: # Shift-PgUp (_, t, _, b) = get_viewport() scroll_buffer(t - b + 2) scrolling = True force_repaint = False elif is_shift_pressed(rec) and rec.VirtualKeyCode == 34: # Shift-PgDn (_, t, _, b) = get_viewport() scroll_buffer(b - t - 2) scrolling = True force_repaint = False else: # Clean key (no modifiers) if recChar == chr(0): # Special key (arrows and such) if rec.VirtualKeyCode == 37: # Left arrow state.handle(ActionCode.ACTION_LEFT, select) elif rec.VirtualKeyCode == 39: # Right arrow state.handle(ActionCode.ACTION_RIGHT, select) elif rec.VirtualKeyCode == 36: # Home state.handle(ActionCode.ACTION_HOME, select) elif rec.VirtualKeyCode == 35: # End state.handle(ActionCode.ACTION_END, select) elif rec.VirtualKeyCode == 38: # Up arrow state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 40: # Down arrow state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 46: # Delete state.handle(ActionCode.ACTION_DELETE) elif recChar == chr(13): # Enter state.history.reset() break elif recChar == chr(27): # Esc if scrolling: scrolling = False else: state.handle(ActionCode.ACTION_ESCAPE) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) auto_select = False elif recChar == '\t': # Tab sys.stdout.write(state.after_cursor) # Move cursor to the end tokens = parse_line(state.before_cursor) if tokens == [] or state.before_cursor[-1] in sep_chars: tokens.append('') # This saves some checks later on if tokens[-1].strip('"').count('%') % 2 == 1: (completed, suggestions) = complete_env_var(state.before_cursor) elif has_wildcards(tokens[-1]): (completed, suggestions) = complete_wildcard(state.before_cursor) else: (completed, suggestions) = complete_file(state.before_cursor) # Show multiple completions if available if len(suggestions) > 1: dir_hist.shown = False # The displayed dirhist is no longer valid column_width = max([len(s) for s in suggestions]) + 10 if column_width > console.get_buffer_size()[0] - 1: column_width = console.get_buffer_size()[0] - 1 if len(suggestions) > (get_viewport()[3] - get_viewport()[1]) / 4: # We print multiple columns to save space num_columns = (console.get_buffer_size()[0] - 1) / column_width else: # We print a single column for clarity num_columns = 1 num_lines = len(suggestions) / num_columns if len(suggestions) % num_columns != 0: num_lines += 1 num_screens = 1.0 * num_lines / (get_viewport()[3] - get_viewport()[1]) if num_screens >= 0.9: # We ask for confirmation before displaying many completions (c_x, c_y) = get_cursor() offset_from_bottom = console.get_buffer_size()[1] - c_y message = ' Scroll ' + str(int(round(num_screens))) + ' screens? [Tab] ' sys.stdout.write('\n' + message) rec = read_input() move_cursor(c_x, console.get_buffer_size()[1] - offset_from_bottom) sys.stdout.write('\n' + ' ' * len(message)) move_cursor(c_x, console.get_buffer_size()[1] - offset_from_bottom) if recChar != '\t': continue sys.stdout.write('\n') for line in range(0, num_lines): # Print one line sys.stdout.write('\r') for column in range(0, num_columns): if line + column * num_lines < len(suggestions): s = suggestions[line + column * num_lines] if has_wildcards(tokens[-1]): # Print wildcard matches in a different color tokens = parse_line(completed.rstrip('\\')) token = tokens[-1].replace('"', '') (_, _, prefix) = token.rpartition('\\') match = wildcard_to_regex(prefix + '*').match(s) current_index = 0 for i in range(1, match.lastindex + 1): sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.completion_match + s[current_index : match.start(i)] + color.Fore.DEFAULT + color.Back.DEFAULT + s[match.start(i) : match.end(i)]) current_index = match.end(i) sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * (column_width - len(s))) else: # Print the common part in a different color common_prefix_len = len(find_common_prefix(state.before_cursor, suggestions)) sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.completion_match + s[:common_prefix_len] + color.Fore.DEFAULT + color.Back.DEFAULT + s[common_prefix_len : ]) sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * (column_width - len(s))) sys.stdout.write('\n') state.reset_prev_line() state.handle(ActionCode.ACTION_COMPLETE, completed) elif rec.Char == chr(8): # Backspace state.handle(ActionCode.ACTION_BACKSPACE) else: # Regular character state.handle(ActionCode.ACTION_INSERT, rec.Char) # Done reading line, now execute sys.stdout.write(state.after_cursor) # Move cursor to the end sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT) line = (state.before_cursor + state.after_cursor).strip() tokens = parse_line(line) if tokens == [] or tokens[0] == '': continue else: print run_command(tokens) # Add to history state.history.add(line) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) # Add to dir history dir_hist.visit_cwd() save_history(dir_hist.locations, pycmd_data_dir + '\\dir_history', dir_hist.max_len)
elif has_wildcards(tokens[-1]): (completed, suggestions) = complete_wildcard(state.before_cursor) else: (completed, suggestions) = complete_file(state.before_cursor) cursor_backward(len(state.before_cursor)) state.handle(ActionCode.ACTION_COMPLETE, completed) stdout.write(state.before_cursor + state.after_cursor) # Show multiple completions if available if len(suggestions) > 1: dir_hist.shown = False # The displayed dirhist is no longer valid column_width = max([len(s) for s in suggestions]) + 10 if column_width > console.get_buffer_size()[0] - 1: column_width = console.get_buffer_size()[0] - 1 if len(suggestions) > (get_viewport()[3] - get_viewport()[1]) / 4: # We print multiple columns to save space num_columns = (console.get_buffer_size()[0] - 1) / column_width else: # We print a single column for clarity num_columns = 1 num_lines = len(suggestions) / num_columns if len(suggestions) % num_columns != 0: num_lines += 1 num_screens = 1.0 * num_lines / (get_viewport()[3] - get_viewport()[1])
def list_and_switch(): winstate_full_path = os.path.join(pycmd_data_dir, windows_state_path) with open(winstate_full_path, 'r') as f: winstate = f.readlines() winstate.reverse() first_line = True index = 0 orig_index = -1 index_map = [] remove_hwnd_list = [] columns = console.get_buffer_size()[0] - 3 currHwnd = py_GetConsoleWindow() for line in winstate: orig_index += 1 states = line.split(winstate_separator) if len(states) != 3: print("Warning: unsupported line for windows switch: ", line) continue hwnd = int(states[0]) if hwnd == currHwnd: continue if not py_IsWindow(hwnd): remove_hwnd_list.append(hwnd) continue curr_index_char = chr(ord('a') + index) index += 1 index_map.append(orig_index) pwd = states[1].strip() + '> ' cmd = states[2].strip() if len(pwd) > columns: pwd = pwd[0: column - 5] + '...> ' cmd = '' else: left_columns = columns - len(pwd) if len(cmd) > left_columns: if left_columns >= 3: cmd = cmd[0:left_columns - 3] + '...' if first_line: sys.stdout.write('\n\n') first_line = False if index % 2 == 0: color_str_cmd = color.Fore.RED + color.Fore.CLEAR_BRIGHT color_str_pwd = color.Fore.RED + color.Fore.SET_BRIGHT else: color_str_cmd = color.Fore.GREEN + color.Fore.CLEAR_BRIGHT color_str_pwd = color.Fore.GREEN + color.Fore.SET_BRIGHT sys.stdout.write(color_str_pwd + curr_index_char + ': ' + pwd + color_str_cmd + cmd + '\n') if index == 0: return sys.stdout.write(color.Fore.DEFAULT + '\n') message = ' Press a-z to switch to target PyCmd, space to ignore: ' sys.stdout.write(message) rec = console.read_input() select_id = ord(rec.Char) - ord('a') #TODO: refresh current line instead of output new line? # Why 1 '\n' doesn't work? Know why, because cmd prompt is up for 1 line, # which occupies the message line scrolled by 1 line #sys.stdout.write('\n\n') sys.stdout.write('\r' + ' ' * len(message)) if 0 <= select_id < index: to_line = winstate[index_map[select_id]] to_line_list = to_line.split(winstate_separator) to_hwnd = int(to_line_list[0]) PyCmdUtils.SwitchToHwnd(to_hwnd) update_window_state(to_line_list[1], to_line_list[2], to_hwnd, remove_hwnd_list)
def list_and_switch(): winstate_full_path = os.path.join(pycmd_data_dir, windows_state_path) with open(winstate_full_path, 'r') as f: winstate = f.readlines() winstate.reverse() first_line = True index = 0 orig_index = -1 index_map = [] remove_hwnd_list = [] columns = console.get_buffer_size()[0] - 3 currHwnd = py_GetConsoleWindow() for line in winstate: orig_index += 1 states = line.split(winstate_separator) if len(states) != 3: print("Warning: unsupported line for windows switch: ", line) continue hwnd = int(states[0]) if hwnd == currHwnd: continue if not py_IsWindow(hwnd): remove_hwnd_list.append(hwnd) continue curr_index_char = chr(ord('a') + index) index += 1 index_map.append(orig_index) pwd = states[1].strip() + '> ' cmd = states[2].strip() if len(pwd) > columns: pwd = pwd[0:column - 5] + '...> ' cmd = '' else: left_columns = columns - len(pwd) if len(cmd) > left_columns: if left_columns >= 3: cmd = cmd[0:left_columns - 3] + '...' if first_line: sys.stdout.write('\n\n') first_line = False if index % 2 == 0: color_str_cmd = color.Fore.RED + color.Fore.CLEAR_BRIGHT color_str_pwd = color.Fore.RED + color.Fore.SET_BRIGHT else: color_str_cmd = color.Fore.GREEN + color.Fore.CLEAR_BRIGHT color_str_pwd = color.Fore.GREEN + color.Fore.SET_BRIGHT sys.stdout.write(color_str_pwd + curr_index_char + ': ' + pwd + color_str_cmd + cmd + '\n') if index == 0: return sys.stdout.write(color.Fore.DEFAULT + '\n') message = ' Press a-z to switch to target PyCmd, space to ignore: ' sys.stdout.write(message) rec = console.read_input() select_id = ord(rec.Char) - ord('a') #TODO: refresh current line instead of output new line? # Why 1 '\n' doesn't work? Know why, because cmd prompt is up for 1 line, # which occupies the message line scrolled by 1 line #sys.stdout.write('\n\n') sys.stdout.write('\r' + ' ' * len(message)) if 0 <= select_id < index: to_line = winstate[index_map[select_id]] to_line_list = to_line.split(winstate_separator) to_hwnd = int(to_line_list[0]) PyCmdUtils.SwitchToHwnd(to_hwnd) update_window_state(to_line_list[1], to_line_list[2], to_hwnd, remove_hwnd_list)
expanded_token = expanded_token + '..\\' tokens[-1] = expanded_token completed = ' '.join(tokens) elif tokens[-1].strip('"').count('%') % 2 == 1: (completed, suggestions) = complete_env_var(state.before_cursor) elif has_wildcards(tokens[-1]): (completed, suggestions) = complete_wildcard(state.before_cursor) else: (completed, suggestions) = complete_file(state.before_cursor) # Show multiple completions if available if len(suggestions) > 1: dir_hist.shown = False # The displayed dirhist is no longer valid column_width = max([len(s) for s in suggestions]) + 10 if column_width > console.get_buffer_size()[0] - 1: column_width = console.get_buffer_size()[0] - 1 if len(suggestions) > (get_viewport()[3] - get_viewport()[1]) / 4: # We print multiple columns to save space num_columns = (console.get_buffer_size()[0] - 1) / column_width else: # We print a single column for clarity num_columns = 1 num_lines = len(suggestions) / num_columns if len(suggestions) % num_columns != 0: num_lines += 1 num_screens = 1.0 * num_lines / (get_viewport()[3] - get_viewport()[1]) if num_screens >= 0.9: # We ask for confirmation before displaying many completions (c_x, c_y) = get_cursor()
def main(): title_prefix = "" # Apply global and user configurations apply_settings(pycmd_install_dir + '\\init.py', (pycmd_install_dir, pycmd_data_dir)) apply_settings(pycmd_data_dir + '\\init.py', (pycmd_install_dir, pycmd_data_dir)) sanitize_settings() init_hooks = get_hooks(hook_types[0]) if len(init_hooks) > 0: for hook in init_hooks.values(): hook() # Parse arguments arg = 1 while arg < len(sys.argv): switch = sys.argv[arg].upper() rest = [ '"' + t + '"' if os.path.exists(t) else t for t in sys.argv[arg + 1:] ] if switch in ['/K', '-K']: # Run the specified command and continue if len(rest) > 0: run_command(rest) dir_hist.visit_cwd() break elif switch in ['/C', '-C']: # Run the specified command end exit if len(rest) > 0: run_command(rest) internal_exit() elif switch in ['/H', '/?', '-H']: # Show usage information and exit print_usage() internal_exit() elif switch in ['/T', '-T']: if arg == len(sys.argv) - 1: sys.stderr.write('PyCmd: no title specified to \'-t\'\n') print_usage() internal_exit() title_prefix = sys.argv[arg + 1] + ' - ' arg += 1 elif switch in ['/I', '-I']: if arg == len(sys.argv) - 1: sys.stderr.write('PyCmd: no script specified to \'-i\'\n') print_usage() internal_exit() apply_settings(sys.argv[arg + 1], (pycmd_install_dir, pycmd_data_dir)) sanitize_settings() arg += 1 elif switch in ['/Q', '-Q']: # Quiet mode: suppress messages behavior.quiet_mode = True else: # Invalid command line switch sys.stderr.write('PyCmd: unrecognized option `' + sys.argv[arg] + '\'\n') print_usage() internal_exit() arg += 1 if not behavior.quiet_mode: # Print some splash text try: from buildinfo import build_info except ImportError: build_info = '<no build info>' print print 'Welcome to PyCmd %s!' % build_info print # Run an empty command to initialize environment run_command(['echo', '>', 'NUL']) # Main loop while True: # Prepare buffer for reading one line state.reset_line(appearance.prompt()) scrolling = False auto_select = False force_repaint = True dir_hist.shown = False print while True: # Update console title and environment curdir = os.getcwd() curdir = curdir[0].upper() + curdir[1:] console.set_console_title(title_prefix + curdir + ' - PyCmd') os.environ['CD'] = curdir if state.changed() or force_repaint: prev_total_len = len( remove_escape_sequences(state.prev_prompt) + state.prev_before_cursor + state.prev_after_cursor) set_cursor_visible(False) cursor_backward( len( remove_escape_sequences(state.prev_prompt) + state.prev_before_cursor)) sys.stdout.write('\r') # Update the offset of the directory history in case of overflow # Note that if the history display is marked as 'dirty' # (dir_hist.shown == False) the result of this action can be # ignored dir_hist.check_overflow(remove_escape_sequences(state.prompt)) # Write current line sys.stdout.write('\r' + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.prompt + state.prompt + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text) line = state.before_cursor + state.after_cursor if state.history.filter == '': sel_start, sel_end = state.get_selection_range() sys.stdout.write(line[:sel_start] + appearance.colors.selection + line[sel_start:sel_end] + color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[sel_end:]) else: pos = 0 colored_line = '' for (start, end) in state.history.current()[1]: colored_line += color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[ pos:start] colored_line += appearance.colors.search_filter + line[ start:end] pos = end colored_line += color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.text + line[ pos:] sys.stdout.write(colored_line) # Erase remaining chars from old line to_erase = prev_total_len - len( remove_escape_sequences(state.prompt) + state.before_cursor + state.after_cursor) if to_erase > 0: sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * to_erase) cursor_backward(to_erase) # Move cursor to the correct position set_cursor_visible(True) cursor_backward(len(state.after_cursor)) # Prepare new input state state.step_line() # Read and process a keyboard event rec = read_input() select = auto_select or is_shift_pressed(rec) # Will be overriden if Shift-PgUp/Dn is pressed force_repaint = not is_control_only(rec) #print '\n\n', rec.keyDown, rec.char, rec.virtualKeyCode, rec.controlKeyState, '\n\n' recChar = rec.CU.Char if PYPY else rec.Char if is_ctrl_pressed( rec) and not is_alt_pressed(rec): # Ctrl-Something if recChar == chr(4): # Ctrl-D if state.before_cursor + state.after_cursor == '': internal_exit('\r\nBye!') else: state.handle(ActionCode.ACTION_DELETE) elif recChar == chr(31): # Ctrl-_ state.handle(ActionCode.ACTION_UNDO_EMACS) auto_select = False elif rec.VirtualKeyCode == 75: # Ctrl-K state.handle(ActionCode.ACTION_KILL_EOL) elif rec.VirtualKeyCode == 32: # Ctrl-Space auto_select = True state.reset_selection() elif rec.VirtualKeyCode == 71: # Ctrl-G if scrolling: scrolling = False else: state.handle(ActionCode.ACTION_ESCAPE) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) auto_select = False elif rec.VirtualKeyCode == 65: # Ctrl-A state.handle(ActionCode.ACTION_HOME, select) elif rec.VirtualKeyCode == 69: # Ctrl-E state.handle(ActionCode.ACTION_END, select) elif rec.VirtualKeyCode == 66: # Ctrl-B state.handle(ActionCode.ACTION_LEFT, select) elif rec.VirtualKeyCode == 70: # Ctrl-F state.handle(ActionCode.ACTION_RIGHT, select) elif rec.VirtualKeyCode == 80: # Ctrl-P state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 78: # Ctrl-N state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 37: # Ctrl-Left state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 39: # Ctrl-Right state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 46: # Ctrl-Delete state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 67: # Ctrl-C # The Ctrl-C signal is caught by our custom handler, and a # synthetic keyboard event is created so that we can catch # it here if state.get_selection() != '': state.handle(ActionCode.ACTION_COPY) else: state.handle(ActionCode.ACTION_ESCAPE) auto_select = False elif rec.VirtualKeyCode == 88: # Ctrl-X state.handle(ActionCode.ACTION_CUT) auto_select = False elif rec.VirtualKeyCode == 87: # Ctrl-W state.handle(ActionCode.ACTION_CUT) auto_select = False elif rec.VirtualKeyCode == 86: # Ctrl-V state.handle(ActionCode.ACTION_PASTE) auto_select = False elif rec.VirtualKeyCode == 89: # Ctrl-Y state.handle(ActionCode.ACTION_PASTE) auto_select = False elif rec.VirtualKeyCode == 8: # Ctrl-Backspace state.handle(ActionCode.ACTION_BACKSPACE_WORD) elif rec.VirtualKeyCode == 90: if not is_shift_pressed(rec): # Ctrl-Z state.handle(ActionCode.ACTION_UNDO) else: # Ctrl-Shift-Z state.handle(ActionCode.ACTION_REDO) auto_select = False elif is_alt_pressed( rec) and not is_ctrl_pressed(rec): # Alt-Something if rec.VirtualKeyCode in [37, 39] + range(49, 59): # Dir history if state.before_cursor + state.after_cursor == '': state.reset_prev_line() if rec.VirtualKeyCode == 37: # Alt-Left changed = dir_hist.go_left() elif rec.VirtualKeyCode == 39: # Alt-Right changed = dir_hist.go_right() else: # Alt-1..Alt-9 changed = dir_hist.jump(rec.VirtualKeyCode - 48) if changed: state.prev_prompt = state.prompt state.prompt = appearance.prompt() save_history(dir_hist.locations, pycmd_data_dir + '\\dir_history', dir_hist.max_len) if dir_hist.shown: dir_hist.display() sys.stdout.write(state.prev_prompt) else: if rec.VirtualKeyCode == 37: # Alt-Left state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 39: # Alt-Right state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 66: # Alt-B state.handle(ActionCode.ACTION_LEFT_WORD, select) elif rec.VirtualKeyCode == 70: # Alt-F state.handle(ActionCode.ACTION_RIGHT_WORD, select) elif rec.VirtualKeyCode == 80: # Alt-P state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 78: # Alt-N state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 68: # Alt-D if state.before_cursor + state.after_cursor == '': dir_hist.display() dir_hist.check_overflow( remove_escape_sequences(state.prev_prompt)) sys.stdout.write(state.prev_prompt) else: state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 87: # Alt-W state.handle(ActionCode.ACTION_COPY) state.reset_selection() auto_select = False elif rec.VirtualKeyCode == 46: # Alt-Delete state.handle(ActionCode.ACTION_DELETE_WORD) elif rec.VirtualKeyCode == 8: # Alt-Backspace state.handle(ActionCode.ACTION_BACKSPACE_WORD) elif rec.VirtualKeyCode == 191: state.handle(ActionCode.ACTION_EXPAND) elif is_shift_pressed( rec) and rec.VirtualKeyCode == 33: # Shift-PgUp (_, t, _, b) = get_viewport() scroll_buffer(t - b + 2) scrolling = True force_repaint = False elif is_shift_pressed( rec) and rec.VirtualKeyCode == 34: # Shift-PgDn (_, t, _, b) = get_viewport() scroll_buffer(b - t - 2) scrolling = True force_repaint = False else: # Clean key (no modifiers) if recChar == chr(0): # Special key (arrows and such) if rec.VirtualKeyCode == 37: # Left arrow state.handle(ActionCode.ACTION_LEFT, select) elif rec.VirtualKeyCode == 39: # Right arrow state.handle(ActionCode.ACTION_RIGHT, select) elif rec.VirtualKeyCode == 36: # Home state.handle(ActionCode.ACTION_HOME, select) elif rec.VirtualKeyCode == 35: # End state.handle(ActionCode.ACTION_END, select) elif rec.VirtualKeyCode == 38: # Up arrow state.handle(ActionCode.ACTION_PREV) elif rec.VirtualKeyCode == 40: # Down arrow state.handle(ActionCode.ACTION_NEXT) elif rec.VirtualKeyCode == 46: # Delete state.handle(ActionCode.ACTION_DELETE) elif recChar == chr(13): # Enter state.history.reset() break elif recChar == chr(27): # Esc if scrolling: scrolling = False else: state.handle(ActionCode.ACTION_ESCAPE) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) auto_select = False elif recChar == '\t': # Tab sys.stdout.write( state.after_cursor) # Move cursor to the end tokens = parse_line(state.before_cursor) if tokens == [] or state.before_cursor[-1] in sep_chars: tokens.append('') # This saves some checks later on if tokens[-1].strip('"').count('%') % 2 == 1: (completed, suggestions) = complete_env_var(state.before_cursor) elif has_wildcards(tokens[-1]): (completed, suggestions) = complete_wildcard(state.before_cursor) else: (completed, suggestions) = complete_file(state.before_cursor) # Show multiple completions if available if len(suggestions) > 1: dir_hist.shown = False # The displayed dirhist is no longer valid column_width = max([len(s) for s in suggestions]) + 10 if column_width > console.get_buffer_size()[0] - 1: column_width = console.get_buffer_size()[0] - 1 if len(suggestions) > (get_viewport()[3] - get_viewport()[1]) / 4: # We print multiple columns to save space num_columns = (console.get_buffer_size()[0] - 1) / column_width else: # We print a single column for clarity num_columns = 1 num_lines = len(suggestions) / num_columns if len(suggestions) % num_columns != 0: num_lines += 1 num_screens = 1.0 * num_lines / (get_viewport()[3] - get_viewport()[1]) if num_screens >= 0.9: # We ask for confirmation before displaying many completions (c_x, c_y) = get_cursor() offset_from_bottom = console.get_buffer_size( )[1] - c_y message = ' Scroll ' + str(int( round(num_screens))) + ' screens? [Tab] ' sys.stdout.write('\n' + message) rec = read_input() move_cursor( c_x, console.get_buffer_size()[1] - offset_from_bottom) sys.stdout.write('\n' + ' ' * len(message)) move_cursor( c_x, console.get_buffer_size()[1] - offset_from_bottom) if recChar != '\t': continue sys.stdout.write('\n') for line in range(0, num_lines): # Print one line sys.stdout.write('\r') for column in range(0, num_columns): if line + column * num_lines < len( suggestions): s = suggestions[line + column * num_lines] if has_wildcards(tokens[-1]): # Print wildcard matches in a different color tokens = parse_line( completed.rstrip('\\')) token = tokens[-1].replace('"', '') (_, _, prefix) = token.rpartition('\\') match = wildcard_to_regex(prefix + '*').match(s) current_index = 0 for i in range(1, match.lastindex + 1): sys.stdout.write( color.Fore.DEFAULT + color.Back.DEFAULT + appearance .colors.completion_match + s[current_index:match.start(i)] + color.Fore.DEFAULT + color.Back.DEFAULT + s[match.start(i):match.end(i)]) current_index = match.end(i) sys.stdout.write( color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * (column_width - len(s))) else: # Print the common part in a different color common_prefix_len = len( find_common_prefix( state.before_cursor, suggestions)) sys.stdout.write( color.Fore.DEFAULT + color.Back.DEFAULT + appearance.colors.completion_match + s[:common_prefix_len] + color.Fore.DEFAULT + color.Back.DEFAULT + s[common_prefix_len:]) sys.stdout.write( color.Fore.DEFAULT + color.Back.DEFAULT + ' ' * (column_width - len(s))) sys.stdout.write('\n') state.reset_prev_line() state.handle(ActionCode.ACTION_COMPLETE, completed) elif rec.Char == chr(8): # Backspace state.handle(ActionCode.ACTION_BACKSPACE) else: # Regular character state.handle(ActionCode.ACTION_INSERT, rec.Char) # Done reading line, now execute sys.stdout.write(state.after_cursor) # Move cursor to the end sys.stdout.write(color.Fore.DEFAULT + color.Back.DEFAULT) line = (state.before_cursor + state.after_cursor).strip() tokens = parse_line(line) if tokens == [] or tokens[0] == '': continue else: print run_command(tokens) # Add to history state.history.add(line) save_history(state.history.list, pycmd_data_dir + '\\history', 1000) # Add to dir history dir_hist.visit_cwd() save_history(dir_hist.locations, pycmd_data_dir + '\\dir_history', dir_hist.max_len)