diff options
| author | Mattias Andrée <maandree@operamail.com> | 2013-07-28 13:10:56 +0200 |
|---|---|---|
| committer | Mattias Andrée <maandree@operamail.com> | 2013-07-28 13:10:56 +0200 |
| commit | 076ecb20c687837267962289fc19cc076d5a9334 (patch) | |
| tree | 4fd7c322da53f747e001e01f4d1d36b31e1e5c2f /src/editor.py | |
| parent | split out updating (diff) | |
| download | pytagomacs-076ecb20c687837267962289fc19cc076d5a9334.tar.gz pytagomacs-076ecb20c687837267962289fc19cc076d5a9334.tar.bz2 pytagomacs-076ecb20c687837267962289fc19cc076d5a9334.tar.xz | |
add editor
Signed-off-by: Mattias Andrée <maandree@operamail.com>
Diffstat (limited to 'src/editor.py')
| -rw-r--r-- | src/editor.py | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/editor.py b/src/editor.py new file mode 100644 index 0000000..8709ccf --- /dev/null +++ b/src/editor.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +''' +featherweight – A lightweight terminal news feed reader + +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 sys +from subprocess import Popen, PIPE + + +''' +GNU Emacs alike text area +''' +class TextArea(): # TODO support small screens + def __init__(self, fields, datamap, left, top, width, height, termsize): + ''' + Constructor + + @param fields:list<str> Field names + @param datamap:dist<str, str> Data map + @param left:int Left position of the component + @param top:int Top position of the component + @param width:int Width of the component + @param height:int Height of the component + @param termsize:(int, int) The height and width of the terminal + ''' + self.fields, self.datamap, self.left, self.top, self.width, self.height, self.termsize = fields, datamap, left, top, width - 1, height, termsize + + + + def run(self, saver): + ''' + Execute text reading + + @param saver Save method + ''' + innerleft = len(max(self.fields, key = len)) + self.left + 3 + + leftlines = [] + datalines = [] + + for key in self.fields: + for line in self.datamap[key].split('\n'): + leftlines.append(key) + datalines.append(line) + + (termh, termw) = self.termsize + (y, x) = (0, 0) + mark = None + + KILL_MAX = 50 + killring = [] + killptr = None + + def status(text): + print('\033[%i;%iH\033[7m%s\033[27m\033[%i;%iH' % (termh - 1, 1, ' (' + text + ') ' + '-' * (termw - len(' (' + text + ') ')), self.top + y, innerleft + x), end='') + + status('unmodified') + + print('\033[%i;%iH' % (self.top, innerleft), end='') + + def alert(text): + if text is None: + alert('') + else: + print('\033[%i;%iH\033[2K%s\033[%i;%iH' % (termh, 1, text, self.top + y, innerleft + x), end='') + + modified = False + override = False + + (oldy, oldx, oldmark) = (y, x, mark) + stored = chr(ord('L') - ord('@')) + alerted = False + edited = False + print('\033[%i;%iH' % (self.top + y, innerleft + x), end='') + while True: + if (oldmark is not None) and (oldmark >= 0): + if oldmark < oldx: + print('\033[%i;%iH\033[49m%s\033[%i;%iH' % (self.top + oldy, innerleft + oldmark, datalines[oldy][oldmark : oldx], self.top + y, innerleft + x), end='') + elif oldmark > oldx: + print('\033[%i;%iH\033[49m%s\033[%i;%iH' % (self.top + oldy, innerleft + oldx, datalines[oldy][oldx : oldmark], self.top + y, innerleft + x), end='') + if (mark is not None) and (mark >= 0): + if mark < x: + print('\033[%i;%iH\033[44;37m%s\033[49;39m\033[%i;%iH' % (self.top + y, innerleft + mark, datalines[y][mark : x], self.top + y, innerleft + x), end='') + elif mark > x: + print('\033[%i;%iH\033[44;37m%s\033[49;39m\033[%i;%iH' % (self.top + y, innerleft + x, datalines[y][x : mark], self.top + y, innerleft + x), end='') + if y != oldy: + if (oldy > 0) and (leftlines[oldy - 1] == leftlines[oldy]) and (leftlines[oldy] == leftlines[-1]): + print('\033[%i;%iH\033[34m%s\033[39m' % (self.top + oldy, self.left, '>'), end='') + else: + print('\033[%i;%iH\033[34m%s:\033[39m' % (self.top + oldy, self.left, leftlines[oldy]), end='') + if (y > 0) and (leftlines[y - 1] == leftlines[y]) and (leftlines[y] == leftlines[-1]): + print('\033[%i;%iH\033[1;34m%s\033[21;39m' % (self.top + y, self.left, '>'), end='') + else: + print('\033[%i;%iH\033[1;34m%s:\033[21;39m' % (self.top + y, self.left, leftlines[y]), end='') + print('\033[%i;%iH' % (self.top + y, innerleft + x), end='') + (oldy, oldx, oldmark) = (y, x, mark) + if edited: + edited = False + if not modified: + modified = True + status('modified' + (' override' if override else '')) + sys.stdout.flush() + if stored is None: + d = sys.stdin.read(1) + else: + d = stored + stored = None + if alerted: + alerted = False + alert(None) + if ord(d) == ord('@') - ord('@'): + if mark is None: + mark = x + alert('Mark set') + elif mark == ~x: + mark = x + alert('Mark activated') + elif mark == x: + mark = ~x + alert('Mark deactivated') + else: + mark = x + alert('Mark set') + alerted = True + elif ord(d) == ord('K') - ord('@'): + if x == len(datalines[y]): + alert('At end') + alerted = True + else: + mark = len(datalines[y]) + stored = chr(ord('W') - ord('@')) + elif ord(d) == ord('W') - ord('@'): + if (mark is not None) and (mark >= 0) and (mark != x): + selected = datalines[y][mark : x] if mark < x else datalines[y][x : mark] + killring.append(selected) + if len(killring) > KILL_MAX: + killring = killring[1:] + stored = chr(127) + else: + alert('No text is selected') + alerted = True + elif ord(d) == ord('Y') - ord('@'): + if len(killring) == 0: + alert('Killring is empty') + alerted = True + else: + mark = None + killptr = len(killring) - 1 + yanked = killring[killptr] + print('\033[%i;%iH%s' % (self.top + y, innerleft + x, yanked + datalines[y][x:]), end='') + datalines[y] = datalines[y][:x] + yanked + datalines[y][x:] + x += len(yanked) + print('\033[%i;%iH' % (self.top + y, innerleft + x), end='') + elif ord(d) == ord('X') - ord('@'): + alert('C-x') + alerted = True + sys.stdout.flush() + d = sys.stdin.read(1) + alert(str(ord(d))) + sys.stdout.flush() + if ord(d) == ord('X') - ord('@'): + if (mark is not None) and (mark >= 0): + x ^= mark; mark ^= x; x ^= mark + alert('Mark swapped') + else: + alert('No mark is activated') + elif ord(d) == ord('S') - ord('@'): + last = '' + for row in range(0, len(datalines)): + current = leftlines[row] + if len(datalines[row].strip()) == 0: + if current is not 'comment': + if current != last: + self.datamap[current] = None + continue + if current == last: + self.datamap[current] += '\n' + datalines[row] + else: + self.datamap[current] = datalines[row] + last = current + saver() + status('unmodified' + (' override' if override else '')) + alert('Saved') + elif ord(d) == ord('C') - ord('@'): + break + else: + stored = d + alerted = False + alert(None) + elif (ord(d) == 127) or (ord(d) == 8): + removed = 1 + if (mark is not None) and (mark >= 0) and (mark != x): + if mark > x: + x ^= mark; mark ^= x; x ^= mark + removed = x - mark + if x == 0: + alert('At beginning') + alerted = True + continue + dataline = datalines[y] + datalines[y] = dataline = dataline[:x - removed] + dataline[x:] + x -= removed + mark = None + print('\033[%i;%iH%s%s\033[%i;%iH' % (self.top + y, innerleft, dataline, ' ' * removed, self.top + y, innerleft + x), end='') + edited = True + elif ord(d) < ord(' '): + if ord(d) == ord('P') - ord('@'): + if y == 0: + alert('At first line') + alerted = True + else: + y -= 1 + mark = None + x = 0 + elif ord(d) == ord('N') - ord('@'): + if y < len(datalines) - 1: + y += 1 + mark = None + x = 0 + elif ord(d) == ord('F') - ord('@'): + if x < len(datalines[y]): + x += 1 + print('\033[C', end='') + else: + alert('At end') + alerted = True + elif ord(d) == ord('B') - ord('@'): + if x > 0: + x -= 1 + print('\033[D', end='') + else: + alert('At beginning') + alerted = True + elif ord(d) == ord('L') - ord('@'): + empty = '\033[0m' + (' ' * self.width + '\n') * len(datalines) + print('\033[%i;%iH%s' % (self.top, self.left, empty), end='') + for row in range(0, len(leftlines)): + leftline = leftlines[row] + ':' + if (leftlines[row - 1] == leftlines[row]) and (leftlines[row] == leftlines[-1]): + leftline = '>' + print('\033[%i;%iH\033[%s34m%s\033[%s39m' % (self.top + row, self.left, '1;' if row == y else '', leftline, '21;' if row == y else ''), end='') + for row in range(0, len(datalines)): + print('\033[%i;%iH%s\033[49m' % (self.top + row, innerleft, datalines[row]), end='') + print('\033[%i;%iH' % (self.top + y, innerleft + x), end='') + elif d == '\033': + d = sys.stdin.read(1) + if d == '[': + d = sys.stdin.read(1) + if d == 'A': + stored = chr(ord('P') - ord('@')) + elif d == 'B': + if y == len(datalines) - 1: + alert('At last line') + alerted = True + else: + stored = chr(ord('N') - ord('@')) + elif d == 'C': + stored = chr(ord('F') - ord('@')) + elif d == 'D': + stored = chr(ord('B') - ord('@')) + elif d == '2': + d = sys.stdin.read(1) + if d == '~': + override = not override + status(('modified' if modified else 'unmodified') + (' override' if override else '')) + elif d == '3': + d = sys.stdin.read(1) + if d == '~': + removed = 1 + if (mark is not None) and (mark >= 0) and (mark != x): + if mark < x: + x ^= mark; mark ^= x; x ^= mark + removed = mark - x + dataline = datalines[y] + if x == len(dataline): + alert('At end') + alerted = True + continue + datalines[y] = dataline = dataline[:x] + dataline[x + removed:] + print('\033[%i;%iH%s%s\033[%i;%iH' % (self.top + y, innerleft, dataline, ' ' * removed, self.top + y, innerleft + x), end='') + mark = None + edited = True + else: + while True: + d = sys.stdin.read(1) + if (ord('a') <= ord(d)) and (ord(d) <= ord('z')): break + if (ord('A') <= ord(d)) and (ord(d) <= ord('Z')): break + if d == '~': break + elif (d == 'w') or (d == 'W'): + if (mark is not None) and (mark >= 0) and (mark != x): + selected = datalines[y][mark : x] if mark < x else datalines[y][x : mark] + killring.append(selected) + mark = None + if len(killring) > KILL_MAX: + killring = killring[1:] + else: + alert('No text is selected') + alerted = True + elif (d == 'y') or (d == 'Y'): + if killptr is not None: + yanked = killring[killptr] + dataline = datalines[y] + if (len(yanked) <= x) and (dataline[x - len(yanked) : x] == yanked): + killptr -= 1 + if killptr < 0: + killptr += len(killring) + dataline = dataline[:x - len(yanked)] + killring[killptr] + dataline[x:] + additional = len(killring[killptr]) - len(yanked) + x += additional + datalines[y] = dataline + print('\033[%i;%iH%s%s\033[%i;%iH' % (self.top + y, innerleft, dataline, ' ' * max(0, -additional), self.top + y, innerleft + x), end='') + else: + stored = chr(ord('Y') - ord('@')) + else: + stored = chr(ord('Y') - ord('@')) + elif d == 'O': + d = sys.stdin.read(1) + if d == 'H': + x = 0 + elif d == 'F': + x = len(datalines[y]) + print('\033[%i;%iH' % (self.top + y, innerleft + x), end='') + elif d == '\n': + stored = chr(ord('N') - ord('@')) + else: + insert = d + if len(insert) == 0: + continue + dataline = datalines[y] + if (not override) or (x == len(dataline)): + print(insert + dataline[x:], end='') + if len(dataline) - x > 0: + print('\033[%iD' % (len(dataline) - x), end='') + datalines[y] = dataline[:x] + insert + dataline[x:] + if (mark is not None) and (mark >= 0): + if mark >= x: + mark += len(insert) + else: + print(insert, end='') + datalines[y] = dataline[:x] + insert + dataline[x + 1:] + x += len(insert) + edited = True + + +def phonysaver(): + pass +print('\033[H\033[2J') +old_stty = Popen('stty --save'.split(' '), stdout = PIPE).communicate()[0] +old_stty = old_stty.decode('utf-8', 'error')[:-1] +Popen('stty -icanon -echo -isig'.split(' '), stdout = PIPE).communicate() +try: + TextArea(['alpha', 'beta'], {'alpha' : 'a', 'beta' : 'be'}, 1, 1, 20, 4, (4, 20)).run(phonysaver) +finally: + print('\033[H\033[2J', end = '') + sys.stdout.flush() + Popen(['stty', old_stty], stdout = PIPE).communicate() + |
