diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/common.py | 156 | ||||
| -rw-r--r-- | src/editor.py | 178 | ||||
| -rw-r--r-- | src/line.py | 2 |
3 files changed, 200 insertions, 136 deletions
diff --git a/src/common.py b/src/common.py new file mode 100644 index 0000000..476cd38 --- /dev/null +++ b/src/common.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +''' +pytagomacs – An Emacs like key–value editor library for Python + +Copyright © 2013 Mattias Andrée (maandree@member.fsf.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +''' +import os + + + +INACTIVE_COLOUR = '34' +''' +:str? The colour of an inactive line +''' + +ACTIVE_COLOUR = '01;34' +''' +:str? The colour of an active line +''' + +SELECTED_COLOUR = '44;37' +''' +:str? The colour of a selected text +''' + +STATUS_COLOUR = '07' +''' +:str? The colour of the status bar +''' + +ALERT_COLOUR = None +''' +:str? The colour of the alert message +''' + +KILLRING_LIMIT = 50 +''' +:int The maximum size of the killring +''' + +EDITRING_LIMIT = 100 +''' +:int The maximum size of the editring +''' + + +atleast = lambda x, minimum : (x is not None) and (x >= minimum) +''' +Test that a value is defined and of at least a minimum value +''' + +limit = lambda x_min, x, x_max : min(max(x_min, x), x_max) +''' +Limit a value to a closed set +''' + +ctrl = lambda key : chr(ord(key) ^ ord('@')) +''' +Return the symbol for a specific letter pressed in combination with Ctrl +''' + +backspace = lambda x : (ord(x) == 127) or (ord(x) == 8) +''' +Check if a key stroke is a backspace key stroke +''' + + + +class Jump(): + ''' + Create a cursor jump that can either be included in a print statement + as a string or invoked + + @param y:int The row, 1 based + @param x:int The column, 1 based + @string :str|()→void Functor that can be treated as a string for jumping + ''' + def __init__(self, y, x): + self.string = '\033[%i;%iH' % (y, x) + def __str__(self): + return self.string + def __call__(self): + print(self.string, end = '') + + +## Load extension and configurations via pytagomacsrc. +config_file = None +# Possible auto-selected configuration scripts, +# earlier ones have precedence, we can only select one. +files = [] +def add_files(var, *ps, multi = False): + if var == '~': + try: + # Get the home (also known as initial) directory of the real user + import pwd + var = pwd.getpwuid(os.getuid()).pw_dir + except: + return + else: + # Resolve environment variable or use empty string if none is selected + if (var is None) or (var in os.environ) and (not os.environ[var] == ''): + var = '' if var is None else os.environ[var] + else: + return + paths = [var] + # Split environment variable value if it is a multi valeu variable + if multi and os.pathsep in var: + paths = [v for v in var.split(os.pathsep) if not v == ''] + # Add files according to patterns + for p in ps: + p = p.replace('/', os.sep).replace('%', 'pytagomacs') + for v in paths: + files.append(v + p) +add_files('XDG_CONFIG_HOME', '/%/%rc', '/%rc') +add_files('HOME', '/.config/%/%rc', '/.config/%rc', '/.%rc') +add_files('~', '/.config/%/%rc', '/.config/%rc', '/.%rc') +add_files('XDG_CONFIG_DIRS', '/%rc', multi = True) +add_files(None, '/etc/%rc') +for file in files: + # If the file we exists, + if os.path.exists(file): + # select it, + config_file = file + # and stop trying files with lower precedence. + break +if config_file is not None: + code = None + # Read configuration script file + with open(config_file, 'rb') as script: + code = script.read() + # Decode configurion script file and add a line break + # at the end to ensure that the last line is empty. + # If it is not, we will get errors. + code = code.decode('utf-8', 'error') + '\n' + # Compile the configuration script, + code = compile(code, config_file, 'exec') + # and run it, with it have the same + # globals as this module, so that it can + # not only use want we have defined, but + # also redefine it for us. + exec(code, globals()) + diff --git a/src/editor.py b/src/editor.py index 0b49e77..bc73348 100644 --- a/src/editor.py +++ b/src/editor.py @@ -18,7 +18,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ''' -import os import sys import string from subprocess import Popen, PIPE @@ -30,151 +29,55 @@ _ = gettext.gettext from killring import * from editring import * +from common import * from line import * -INACTIVE_COLOUR = '34' -''' -:str? The colour of an inactive line -''' - -ACTIVE_COLOUR = '01;34' -''' -:str? The colour of an active line -''' - -SELECTED_COLOUR = '44;37' -''' -:str? The colour of a selected text -''' - -STATUS_COLOUR = '07' -''' -:str? The colour of the status bar -''' - -ALERT_COLOUR = None -''' -:str? The colour of the alert message -''' - -KILLRING_LIMIT = 50 -''' -:int The maximum size of the killring -''' - -EDITRING_LIMIT = 100 -''' -:int The maximum size of the editring -''' - - -atleast = lambda x, minimum : (x is not None) and (x >= minimum) -''' -Test that a value is defined and of at least a minimum value -''' - -limit = lambda x_min, x, x_max : min(max(x_min, x), x_max) -''' -Limit a value to a closed set -''' - -ctrl = lambda key : chr(ord(key) ^ ord('@')) -''' -Return the symbol for a specific letter pressed in combination with Ctrl -''' +## TODO widthless characters should be ignored when calculating the size a text +## TODO implement undo history +## +## Until the user has halted for 1 second (configurably) or has navigated using arrow keys or alternative key combinations, +## edits should be accumulated and then stored in the editring. The edits is stored when the next keystroke is made, there +## should not be a timer waits for the user to idle. +## -backspace = lambda x : (ord(x) == 127) or (ord(x) == 8) -''' -Check if a key stroke is a backspace key stroke -''' +_copy, _cut, _kill, _delete, _erase = Line.copy, Line.cut, Line.kill, Line.delete, Line.erase +_yank, _yank_cycle, _move_point = Line.yank, Line.yank_cycle, Line.move_point +_swap_mark, _override = Line.swap_mark, Line.override +## Editing methods to wrap for undo history +def full_edit(self, func): + # TODO commit changes, if any + rc = func(self) + # TODO commit changes, if any + # TODO reset + return rc -## Load extension and configurations via pytagomacsrc. -config_file = None -# Possible auto-selected configuration scripts, -# earlier ones have precedence, we can only select one. -files = [] -def add_files(var, *ps, multi = False): - if var == '~': - try: - # Get the home (also known as initial) directory of the real user - import pwd - var = pwd.getpwuid(os.getuid()).pw_dir - except: - return +def partial_edit(self, func, with_return = True): + # TODO commit changes, if any, if one second has elapsed, and then reset + if with_return: + return func(self) else: - # Resolve environment variable or use empty string if none is selected - if (var is None) or (var in os.environ) and (not os.environ[var] == ''): - var = '' if var is None else os.environ[var] - else: - return - paths = [var] - # Split environment variable value if it is a multi valeu variable - if multi and os.pathsep in var: - paths = [v for v in var.split(os.pathsep) if not v == ''] - # Add files according to patterns - for p in ps: - p = p.replace('/', os.sep).replace('%', 'pytagomacs') - for v in paths: - files.append(v + p) -add_files('XDG_CONFIG_HOME', '/%/%rc', '/%rc') -add_files('HOME', '/.config/%/%rc', '/.config/%rc', '/.%rc') -add_files('~', '/.config/%/%rc', '/.config/%rc', '/.%rc') -add_files('XDG_CONFIG_DIRS', '/%rc', multi = True) -add_files(None, '/etc/%rc') -for file in files: - # If the file we exists, - if os.path.exists(file): - # select it, - config_file = file - # and stop trying files with lower precedence. - break -if config_file is not None: - code = None - # Read configuration script file - with open(config_file, 'rb') as script: - code = script.read() - # Decode configurion script file and add a line break - # at the end to ensure that the last line is empty. - # If it is not, we will get errors. - code = code.decode('utf-8', 'error') + '\n' - # Compile the configuration script, - code = compile(code, config_file, 'exec') - # and run it, with it have the same - # globals as this module, so that it can - # not only use want we have defined, but - # also redefine it for us. - exec(code, globals()) - + func(self) +break_edit = lambda self, func : full_edit(self, func) -class Jump(): - ''' - Create a cursor jump that can either be included in a print statement - as a string or invoked - - @param y:int The row, 1 based - @param x:int The column, 1 based - @string :str|()→void Functor that can be treated as a string for jumping - ''' - def __init__(self, y, x): - self.string = '\033[%i;%iH' % (y, x) - def __str__(self): - return self.string - def __call__(self): - print(self.string, end = '') +Line.copy = lambda self : break_edit(self, _copy) +Line.cut = lambda self : full_edit(self, _cut) +Line.kill = lambda self : full_edit(self, _kill) +Line.yank = lambda self : partial_edit(self, _yank) +Line.yank_cycle = lambda self : partial_edit(self, _yank_cycle) +Line.swap_mark = lambda self : break_edit(self, _swap_mark) +def __move_point(self, delta): + return break_edit(self, lambda s : _move_point(s, delta)) +Line.move_point = __move_point -## TODO widthless characters should be ignored when calculating the size a text +def __override(self, insert, override = True): + partial_edit(self, lambda s : _override(s, insert, override), False); +Line.override = __override -## TODO implement undo history -## -## Until the user has halted for 1 second (configurably) or has navigated using arrow keys or alternative key combinations, -## edits should be accumulated and then stored in the editring. The edits is stored when the next keystroke is made, there -## should not be a timer waits for the user to idle. -## class TextArea(): ''' @@ -370,12 +273,16 @@ class TextArea(): def ensure_y(): nonlocal stored + updates = False if self.y < self.offy: self.offy = self.y + updates = True if self.y - self.offy > self.height - 3: self.offy = self.y - self.height + 3 - update_status() - redraw() + updates = True + if updates: + update_status() + redraw() def letter_type(char): ## XXX how do we do this with unicode support return (char in string.whitespace) or (char in string.punctuation) @@ -453,6 +360,7 @@ class TextArea(): elif d == ctrl('Y'): edit(lambda L : L.yank(), _('Killring is empty')) elif d == ctrl('R'): self.editring.change_direction() elif d in (ctrl('_'), ctrl('U')): + ## TODO history break if self.editring.is_empty(): self.alert(_('Nothing to undo')) else: diff --git a/src/line.py b/src/line.py index 1355a38..4ec4000 100644 --- a/src/line.py +++ b/src/line.py @@ -18,7 +18,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ''' -from editor import * +from common import * class Line(): |
