Permalink
executable file 340 lines (247 sloc) 8.47 KB
#!/usr/bin/env python
"""
A simple example of a Notepad-like text editor.
"""
from __future__ import unicode_literals
from prompt_toolkit.application import Application
from prompt_toolkit.application.current import get_app
from prompt_toolkit.completion import PathCompleter
from prompt_toolkit.eventloop import Future, ensure_future, Return, From
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout.containers import Float, HSplit, VSplit, Window, WindowAlign, ConditionalContainer
from prompt_toolkit.layout.controls import FormattedTextControl
from prompt_toolkit.layout.dimension import D
from prompt_toolkit.layout.layout import Layout
from prompt_toolkit.layout.menus import CompletionsMenu
from prompt_toolkit.lexers import DynamicLexer, PygmentsLexer
from prompt_toolkit.search import start_search
from prompt_toolkit.styles import Style
from prompt_toolkit.widgets import Dialog, Label, Button, TextArea, SearchToolbar, MenuContainer, MenuItem
import datetime
class ApplicationState:
"""
Application state.
For the simplicity, we store this as a global, but better would be to
instantiate this as an object and pass at around.
"""
show_status_bar = True
current_path = None
def get_statusbar_text():
return ' Press Ctrl-C to open menu. '
def get_statusbar_right_text():
return ' {}:{} '.format(
text_field.document.cursor_position_row + 1,
text_field.document.cursor_position_col + 1)
search_toolbar = SearchToolbar()
text_field = TextArea(
lexer=DynamicLexer(
lambda: PygmentsLexer.from_filename(
ApplicationState.current_path or '.txt',
sync_from_start=False)),
scrollbar=True,
line_numbers=True,
search_field=search_toolbar,
)
class TextInputDialog(object):
def __init__(self, title='', label_text='', completer=None):
self.future = Future()
def accept_text(buf):
get_app().layout.focus(ok_button)
buf.complete_state = None
return True
def accept():
self.future.set_result(self.text_area.text)
def cancel():
self.future.set_result(None)
self.text_area = TextArea(
completer=completer,
multiline=False,
width=D(preferred=40),
accept_handler=accept_text)
ok_button = Button(text='OK', handler=accept)
cancel_button = Button(text='Cancel', handler=cancel)
self.dialog = Dialog(
title=title,
body=HSplit([
Label(text=label_text),
self.text_area
]),
buttons=[ok_button, cancel_button],
width=D(preferred=80),
modal=True)
def __pt_container__(self):
return self.dialog
class MessageDialog(object):
def __init__(self, title, text):
self.future = Future()
def set_done():
self.future.set_result(None)
ok_button = Button(text='OK', handler=(lambda: set_done()))
self.dialog = Dialog(
title=title,
body=HSplit([
Label(text=text),
]),
buttons=[ok_button],
width=D(preferred=80),
modal=True)
def __pt_container__(self):
return self.dialog
body = HSplit([
text_field,
search_toolbar,
ConditionalContainer(
content=VSplit([
Window(FormattedTextControl(get_statusbar_text), style='class:status'),
Window(FormattedTextControl(get_statusbar_right_text),
style='class:status.right', width=9, align=WindowAlign.RIGHT),
], height=1),
filter=Condition(lambda: ApplicationState.show_status_bar)),
])
# Global key bindings.
bindings = KeyBindings()
@bindings.add('c-c')
def _(event):
" Focus menu. "
event.app.layout.focus(root_container.window)
#
# Handlers for menu items.
#
def do_open_file():
def coroutine():
global current_path
open_dialog = TextInputDialog(
title='Open file',
label_text='Enter the path of a file:',
completer=PathCompleter())
path = yield From(show_dialog_as_float(open_dialog))
current_path = path
if path is not None:
try:
with open(path, 'rb') as f:
text_field.text = f.read().decode('utf-8', errors='ignore')
except IOError as e:
show_message('Error', '{}'.format(e))
ensure_future(coroutine())
def do_about():
show_message('About', 'Text editor demo.\nCreated by Jonathan Slenders.')
def show_message(title, text):
def coroutine():
dialog = MessageDialog(title, text)
yield From(show_dialog_as_float(dialog))
ensure_future(coroutine())
def show_dialog_as_float(dialog):
" Coroutine. "
float_ = Float(content=dialog)
root_container.floats.insert(0, float_)
app = get_app()
focused_before = app.layout.current_window
app.layout.focus(dialog)
result = yield dialog.future
app.layout.focus(focused_before)
if float_ in root_container.floats:
root_container.floats.remove(float_)
raise Return(result)
def do_new_file():
text_field.text = ''
def do_exit():
get_app().exit()
def do_time_date():
text = datetime.datetime.now().isoformat()
text_field.buffer.insert_text(text)
def do_go_to():
def coroutine():
dialog = TextInputDialog(
title='Go to line',
label_text='Line number:')
line_number = yield From(show_dialog_as_float(dialog))
try:
line_number = int(line_number)
except ValueError:
show_message('Invalid line number')
else:
text_field.buffer.cursor_position = \
text_field.buffer.document.translate_row_col_to_index(line_number - 1, 0)
ensure_future(coroutine())
def do_undo():
text_field.buffer.undo()
def do_cut():
data = text_field.buffer.cut_selection()
get_app().clipboard.set_data(data)
def do_copy():
data = text_field.buffer.copy_selection()
get_app().clipboard.set_data(data)
def do_delete():
text_field.buffer.cut_selection()
def do_find():
start_search(text_field.control)
def do_find_next():
search_state = get_app().current_search_state
cursor_position = text_field.buffer.get_search_position(
search_state, include_current_position=False)
text_field.buffer.cursor_position = cursor_position
def do_paste():
text_field.buffer.paste_clipboard_data(get_app().clipboard.get_data())
def do_select_all():
text_field.buffer.cursor_position = 0
text_field.buffer.start_selection()
text_field.buffer.cursor_position = len(text_field.buffer.text)
def do_status_bar():
ApplicationState.show_status_bar = not ApplicationState.show_status_bar
#
# The menu container.
#
root_container = MenuContainer(body=body, menu_items=[
MenuItem('File', children=[
MenuItem('New...', handler=do_new_file),
MenuItem('Open...', handler=do_open_file),
MenuItem('Save'),
MenuItem('Save as...'),
MenuItem('-', disabled=True),
MenuItem('Exit', handler=do_exit),
]),
MenuItem('Edit', children=[
MenuItem('Undo', handler=do_undo),
MenuItem('Cut', handler=do_cut),
MenuItem('Copy', handler=do_copy),
MenuItem('Paste', handler=do_paste),
MenuItem('Delete', handler=do_delete),
MenuItem('-', disabled=True),
MenuItem('Find', handler=do_find),
MenuItem('Find next', handler=do_find_next),
MenuItem('Replace'),
MenuItem('Go To', handler=do_go_to),
MenuItem('Select All', handler=do_select_all),
MenuItem('Time/Date', handler=do_time_date),
]),
MenuItem('View', children=[
MenuItem('Status Bar', handler=do_status_bar),
]),
MenuItem('Info', children=[
MenuItem('About', handler=do_about),
]),
], floats=[
Float(xcursor=True,
ycursor=True,
content=CompletionsMenu(
max_height=16,
scroll_offset=1)),
], key_bindings=bindings)
style = Style.from_dict({
'status': 'reverse',
'shadow': 'bg:#440044',
})
layout = Layout(
root_container,
focused_element=text_field)
application = Application(
layout=layout,
enable_page_navigation_bindings=True,
style=style,
mouse_support=True,
full_screen=True)
def run():
application.run()
if __name__ == '__main__':
run()