Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Text display help
-
How do I edit a file while seeing what I'm editing, using python? Kinda like a text editor.
-
Please send a screenshot of your code in Pythonista.
-
Edit:
removed
f.close()
and switched to usingenumerate
in forloop with history.a lil messy but its a simple text editor.. i left out the input view for passing what file but it shows undo/redo, and opens and displays plain text and saves changes on close.
import ui def new_button(frame, title, action): btn=ui.Button() btn.frame=frame btn.background_color='#686868' btn.tint_color='#000000' btn.title=title btn.action=action return btn class Text_Editor: def __init__(self, main, tv, text_file, *args, **kwargs): # undo/redo View self.main=main self.text_file=text_file self.active_file=None self.history_view = ui.TextView( text='UNDO HISTORY', frame=(250, 275, 300, 400), editable=False, border_width=5, background_color='#dedede') # Undo Button undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo) main.add_subview(undo_btn) # Redo Button redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo) main.add_subview(redo_btn) # Close Button close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close) main.add_subview(close_btn) self.history=[] self.current_index=len(self.history) self.textview=tv self.textview_should_begin_editing(self.textview) def append(self, text): self.main.remove_subview(self.history_view) if self.current_index < len(self.history): self.history=self.history[:self.current_index] self.current_index=len(self.history) self.history.append(text) self.current_index+=1 self.update_history(self.current_index) def update_history(self, index): self.history_view.text='' for i, x in enumerate(self.history): if i == index: self.history_view.text+=str(i)+' '+x+' <--'+'\n' elif x == self.history[-1] and index == len(self.history): self.history_view.text+=str(i)+' '+x+' <--'+'\n' else: self.history_view.text+=str(i)+' '+x+'\n' def close(self, sender): with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) sender.superview.close() def redo(self, sender): if self.current_index == len(self.history): return if self.current_index < len(self.history): self.current_index+=1 self.textview.text = self.history[self.current_index-1] self.update_history(self.current_index-1) def undo(self, sender): if self.current_index == 0: return self.main.add_subview(self.history_view) self.current_index-=1 self.textview.text = self.history[self.current_index-1] self.update_history(self.current_index-1) def textview_should_begin_editing(self, textview): if textview.text=='' and self.active_file ==None: if self.text_file: with open(self.text_file, 'r') as f: self.active_file=f for line in f: textview.text+=line self.textview=textview return True def textview_did_change(self, textview): self.append(textview.text) self.update_history(self.current_index) pass class Main(ui.View): def __init__(self, *args, **kwargs): text_file='./TextView.py' # Main Window self.width = 650 self.height = 1000 self.background_color="#8b8b8b" self.update_interval=1 # Text Input Window self.tv = ui.TextView( text='', frame=(25, 50, 600, 875), background_color='#dedede') self.add_subview(self.tv) self.tv.delegate=Text_Editor(self, self.tv, text_file) if __name__ == '__main__': Main().present('sheet', hide_title_bar=True)
-
i=0 for x in self.history: [...] i+=1
-->
for i, x in enumerate(self.history):
https://docs.python.org/3/library/functions.html#enumerate
with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) f.close() # <-- this line is not needed. `with open() as` automates the call to `close()`.
https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
-
@ccc said:
i=0 for x in self.history: [...] i+=1
-->
for i, x in enumerate(self.history):
https://docs.python.org/3/library/functions.html#enumerateGood call! i keep forgetting about
enumerate()
@ccc said:
with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) f.close() # <-- this line is not needed. `with open() as` automates the call to `close()`.
https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
f.close()
is more of a typo. i tend to write everything and jump around alot because of my Anxiety and ADHD 😜🤕 and i missstuff during refactoring. thankyou for the input and corection 😁 ill change out forenumerate
and get rid of the pointlessf.close()
-
Do you have a file in the current working directory with the name
TextView.py
? -
Never mind. I found what to edit
-
-
I couldn’t find what variable to edit to get to the right file, but now I have
-
@Bumbo-Cactoni oh ok i meant to Make note of where that was originally..
i think i saw younsay somthing about it slowing down when you typed?
-
@stephen
I don’t know exactly why, but the longer I type for, the slower the program gets. It’s not a big issue, as I can just close the text editor and reopen it. -
@Bumbo-Cactoni, I am sure this was just a quick prototype, as there is a lot happening every time you type a single letter. @stephen, maybe as a quick fix, remove all the history stuff as it is not necessary here? Also, consider saving to file in a background thread, and only whenever there is e.g. a 500 ms break in the typing action.
... Sorry, disregard the second point, as I see the saving only happens when closing.
-
@stephen
I agree with mikael. His point is valid: I don’t really need the history stuff, and just in case I do, I can put all the history stuff in a separate text file, so I can put it in if needed. -
Alright. I have figured out how to make it work faster, without deleting the code. Just make all the update_history, undo, and redo into comments:
import ui def new_button(frame, title, action): btn=ui.Button() btn.frame=frame btn.background_color='#686868' btn.tint_color='#000000' btn.title=title btn.action=action return btn class Text_Editor: def __init__(self, main, tv, text_file, *args, **kwargs): # undo/redo View self.main=main self.text_file=text_file self.active_file=None self.history_view = ui.TextView( text='UNDO HISTORY', frame=(250, 275, 300, 400), editable=False, border_width=5, background_color='#dedede') # Undo Button # undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo) # main.add_subview(undo_btn) # Redo Button # redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo) # main.add_subview(redo_btn) # Close Button close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close) main.add_subview(close_btn) self.history=[] self.current_index=len(self.history) self.textview=tv self.textview_should_begin_editing(self.textview) def append(self, text): self.main.remove_subview(self.history_view) if self.current_index < len(self.history): self.history=self.history[:self.current_index] self.current_index=len(self.history) self.history.append(text) self.current_index+=1 # self.update_history(self.current_index) # def update_history(self, index): # self.history_view.text='' # for i, x in enumerate(self.history): # if i == index: # self.history_view.text+=str(i)+' '+x+' <--'+'\n' # elif x == self.history[-1] and index == len(self.history): # self.history_view.text+=str(i)+' '+x+' <--'+'\n' # else: # self.history_view.text+=str(i)+' '+x+'\n' def close(self, sender): with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) sender.superview.close() # def redo(self, sender): # if self.current_index == len(self.history): # return # if self.current_index < len(self.history): # self.current_index+=1 # self.textview.text = self.history[self.current_index-1] # self.update_history(self.current_index-1) # def undo(self, sender): # if self.current_index == 0: # return # self.main.add_subview(self.history_view) # self.current_index-=1 # self.textview.text = self.history[self.current_index-1] # self.update_history(self.current_index-1) def textview_should_begin_editing(self, textview): if textview.text=='' and self.active_file ==None: if self.text_file: with open(self.text_file, 'r') as f: self.active_file=f for line in f: textview.text+=line self.textview=textview return True def textview_did_change(self, textview): self.append(textview.text) # self.update_history(self.current_index) pass class Main(ui.View): def __init__(self, *args, **kwargs): """THIS IS WHERE YOU EDIT THE FILE!""" text_file='./file.txt' # Main Window self.width = 650 self.height = 1000 self.background_color="#8b8b8b" self.update_interval=1 # Text Input Window self.tv = ui.TextView( text='', frame=(25, 50, 600, 875), background_color='#dedede') self.add_subview(self.tv) self.tv.delegate=Text_Editor(self, self.tv, text_file) if __name__ == '__main__': Main().present('sheet', hide_title_bar=True) ```
-
@Bumbo-Cactoni im not 100% on understanding exactly what you are going for so ive just beenthrowin stuff together to help ya out. ive been running under the assumption of a text editor lol thats why i included the undo funtionality, and currently playing around with a custom file picker.
as for your workaroud, @mikael was correct about history update. but you only needed to remove the history_view stuff you can keep the undo-redo stuff if you like. and i would suggest not storing history in a file unless yourvwanting to keep a log of changes. i say this because history is just the ontainer holding the diferent states of your text for undo-redo. and changes often and you wouldnt want to open and close a file lot to keep it updated.
-
@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor! -
@Bumbo-Cactoni, we still do not know why you need this type of a text editor, and do not just use Pythonista’s editor.
Use google or other search engine to search for ”omz forum file picker dialog”, and you will find something, I am sure.
-
@Bumbo-Cactoni said:
@stephen
Ok, thanks! Also, yes, I was aiming for a text editor! If you figure out the custom file picker, let me know! I think that would be awesome to have in a text editor!here is the text editor with the file picker added at the start. this was a fun one so i may contenu to add onto this myself. Though i do agree with @mikael.. Im not picturing why you want this unless just for learning? or is this meant for some type of note taking mecanic to a game or app?
import ui import os from objc_util import ObjCInstance, ObjCClass from operator import attrgetter import time import threading import functools import ftplib import re # http://stackoverflow.com/a/6547474 def human_size(size_bytes): '''Helper function for formatting human-readable file sizes''' if size_bytes == 1: return "1 byte" suffixes_table = [('bytes',0),('KB',0),('MB',1),('GB',2),('TB',2), ('PB',2)] num = float(size_bytes) for suffix, precision in suffixes_table: if num < 1024.0: break num /= 1024.0 if precision == 0: formatted_size = "%d" % num else: formatted_size = str(round(num, ndigits=precision)) return "%s %s" % (formatted_size, suffix) class TreeNode (object): def __init__(self): self.expanded = False self.children = None self.leaf = True self.title = '' self.subtitle = '' self.icon_name = None self.level = 0 self.enabled = True def expand_children(self): self.expanded = True self.children = [] def collapse_children(self): self.expanded = False def __repr__(self): return '<TreeNode: "%s"%s>' % (self.title, ' (expanded)' if self.expanded else '') class FileTreeNode (TreeNode): def __init__(self, path, show_size=True, select_dirs=True, file_pattern=None): TreeNode.__init__(self) self.path = path self.title = os.path.split(path)[1] self.select_dirs = select_dirs self.file_pattern = file_pattern is_dir = os.path.isdir(path) self.leaf = not is_dir ext = os.path.splitext(path)[1].lower() if is_dir: self.icon_name = 'iob:filing_24' elif ext == '.py': self.icon_name = 'iob:code_24' elif ext == '.pyui': self.icon_name = 'UI File' elif ext in ('.png', '.jpg', '.jpeg', '.gif'): self.icon_name = 'iob:image_24' elif ext == '.txt': self.icon_name = 'iob:compose_24' elif ext == '.md': self.icon_name = 'iob:ios7_bookmarks_outline_24' elif ext == '.html': self.icon_name = 'iob:leaf_24' elif ext == '.css': self.icon_name = 'CSS File' else: self.icon_name = 'FileOther' self.show_size = show_size if not is_dir and show_size: self.subtitle = human_size((os.stat(self.path).st_size)) if is_dir and not select_dirs: self.enabled = False elif not is_dir: filename = os.path.split(path)[1] for x in file_pattern: if x in filename: self.enabled = True break @property def cmp_title(self): return self.title def expand_children(self): if self.children is not None: self.expanded = True return files = os.listdir(self.path) children = [] for filename in files: if filename.startswith('.'): continue full_path = os.path.join(self.path, filename) node = FileTreeNode(full_path, self.show_size, self.select_dirs, self.file_pattern) node.level = self.level + 1 children.append(node) self.expanded = True self.children = sorted(children, key=attrgetter('leaf', 'cmp_title')) class TreeDialogController (object): def __init__(self, main, root_node, x, y, allow_multi=False, async_mode=False): self.main=main self.async_mode = async_mode self.allow_multi = allow_multi self.selected_entries = None self.table_view = ui.TableView() self.table_view.frame = (0, 0, 500, 500) self.table_view.data_source = self self.table_view.delegate = self self.table_view.flex = 'WH' self.table_view.border_width=5 self.table_view.corner_radius=8.0 self.table_view.allows_multiple_selection = False self.table_view.background_color='#daba8b' self.table_view.tint_color = '#ff00d1' self.view = ui.View(frame=(x, y, 500, 500)) self.view.add_subview(self.table_view) self.view.name = root_node.title self.busy_view = ui.View(frame=self.view.bounds, flex='WH', background_color=(0, 0, 0, 0.35)) hud = ui.View(frame=(self.view.center.x - 50, self.view.center.y - 50, 100, 100)) hud.background_color = (0, 0, 0, 0.7) hud.corner_radius = 8.0 hud.flex = 'TLRB' spinner = ui.ActivityIndicator() spinner.style = ui.ACTIVITY_INDICATOR_STYLE_WHITE_LARGE spinner.center = (50, 50) spinner.start_animating() hud.add_subview(spinner) self.busy_view.add_subview(hud) self.busy_view.alpha = 0.0 self.view.add_subview(self.busy_view) self.done_btn = ui.ButtonItem(title='Done', action=self.done_action) if self.allow_multi: self.view.right_button_items = [self.done_btn] self.done_btn.enabled = False self.root_node = root_node self.entries = [] self.flat_entries = [] if self.async_mode: self.set_busy(True) t = threading.Thread(target=self.expand_root) t.start() else: self.expand_root() def expand_root(self): self.root_node.expand_children() self.set_busy(False) self.entries = self.root_node.children self.flat_entries = self.entries self.table_view.reload() def flatten_entries(self, entries, dest=None): if dest is None: dest = [] for entry in entries: dest.append(entry) if not entry.leaf and entry.expanded: self.flatten_entries(entry.children, dest) return dest def rebuild_flat_entries(self): self.flat_entries = self.flatten_entries(self.entries) def tableview_number_of_rows(self, tv, section): return len(self.flat_entries) def tableview_cell_for_row(self, tv, section, row): cell = ui.TableViewCell() cell.background_color='#daba8b' entry = self.flat_entries[row] level = entry.level - 1 image_view = ui.ImageView(frame=(44 + 20*level, 5, 34, 34)) label_x = 44+34+8+20*level label_w = cell.content_view.bounds.w - label_x - 8 if entry.subtitle: label_frame = (label_x, 0, label_w, 26) sub_label = ui.Label(frame=(label_x, 26, label_w, 14)) sub_label.font = ('<System>', 12) sub_label.text = entry.subtitle sub_label.text_color = '#2a8a99' cell.content_view.add_subview(sub_label) else: label_frame = (label_x, 0, label_w, 44) label = ui.Label(frame=label_frame) if entry.subtitle: label.font = ('<System>', 15) else: label.font = ('<System>', 18) label.text = entry.title label.flex = 'W' cell.content_view.add_subview(label) if entry.leaf and not entry.enabled: label.text_color = '#66006b' cell.content_view.add_subview(image_view) if not entry.leaf: has_children = entry.expanded btn = ui.Button(image=ui.Image.named('iob:minus_round_24' if has_children else 'iob:plus_round_24')) btn.frame = (20*level, 0, 44, 44) btn.action = self.expand_dir_action cell.content_view.add_subview(btn) if entry.icon_name: image_view.image = ui.Image.named(entry.icon_name) else: image_view.image = None cell.selectable = entry.enabled return cell def row_for_view(self, sender): '''Helper to find the row index for an 'expand' button''' cell = ObjCInstance(sender) while not cell.isKindOfClass_(ObjCClass('UITableViewCell')): cell = cell.superview() return ObjCInstance(self.table_view).indexPathForCell_(cell).row() def expand_dir_action(self, sender): '''Invoked by 'expand' button''' row = self.row_for_view(sender) entry = self.flat_entries[row] if entry.expanded: sender.image = ui.Image.named('iob:plus_round_24') else: sender.image = ui.Image.named('iob:minus_round_24') self.toggle_dir(row) self.update_done_btn() def toggle_dir(self, row): '''Expand or collapse a folder node''' entry = self.flat_entries[row] if entry.expanded: entry.collapse_children() old_len = len(self.flat_entries) self.rebuild_flat_entries() num_deleted = old_len - len(self.flat_entries) deleted_rows = range(row + 1, row + num_deleted + 1) self.table_view.delete_rows(deleted_rows) else: if self.async_mode: self.set_busy(True) expand = functools.partial(self.do_expand, entry, row) t = threading.Thread(target=expand) t.start() else: self.do_expand(entry, row) def do_expand(self, entry, row): '''Actual folder expansion (called on background thread if async_mode is enabled)''' entry.expand_children() self.set_busy(False) old_len = len(self.flat_entries) self.rebuild_flat_entries() num_inserted = len(self.flat_entries) - old_len inserted_rows = range(row + 1, row + num_inserted + 1) self.table_view.insert_rows(inserted_rows) def tableview_did_select(self, tv, section, row): self.update_done_btn() def tableview_did_deselect(self, tv, section, row): self.update_done_btn() def update_done_btn(self): '''Deactivate the done button when nothing is selected''' selected = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled] # print(selected) if selected and not self.allow_multi: self.done_action(None) else: self.done_btn.enabled = len(selected) > 0 def set_busy(self, flag): '''Show/hide spinner overlay''' def anim(): self.busy_view.alpha = 1.0 if flag else 0.0 ui.animate(anim) def done_action(self, sender): self.selected_entries = [self.flat_entries[i[1]] for i in self.table_view.selected_rows if self.flat_entries[i[1]].enabled] self.main.open_file(self, self.selected_entries[0].path) def file_picker_dialog(parent, x, y, title=None, root_dir=None, multiple=False, select_dirs=False, file_pattern=None, show_size=True): if root_dir is None: root_dir = os.path.expanduser('..') if title is None: title = os.path.split(root_dir)[1] root_node = FileTreeNode(root_dir, show_size, select_dirs, file_pattern) root_node.title = title or '' picker = TreeDialogController(parent, root_node, x, y, allow_multi=multiple) parent.add_subview(picker.view) picker.view.wait_modal() if picker.selected_entries is None: return None paths = [e.path for e in picker.selected_entries] if multiple: return paths else: return paths[0] def new_button(frame, title, action): btn=ui.Button() btn.frame=frame btn.background_color='#686868' btn.tint_color='#000000' btn.title=title btn.action=action return btn class Text_Editor: def __init__(self, main, tv, text_file, *args, **kwargs): # undo/redo View self.main=main self.text_file=text_file self.active_file=None # Undo Button undo_btn=new_button(frame=(25, 2, 100, 46), title='undo', action=self.undo) main.add_subview(undo_btn) # Redo Button redo_btn=new_button(frame=(127, 2, 100, 46), title='redo', action=self.redo) main.add_subview(redo_btn) # Close Button close_btn=new_button(frame=(525, 2, 100, 46), title='close', action=self.close) main.add_subview(close_btn) self.history=[] self.current_index=len(self.history) self.textview=tv self.textview_should_begin_editing(self.textview) def append(self, text): if self.current_index < len(self.history): self.history=self.history[:self.current_index] self.current_index=len(self.history) self.history.append(text) self.current_index+=1 def close(self, sender): with open(self.text_file, 'w') as f: f.writelines(self.textview.text.splitlines(keepends=True)) sender.superview.close() def redo(self, sender): if self.current_index == len(self.history): return if self.current_index < len(self.history): self.current_index+=1 self.textview.text = self.history[self.current_index-1] def undo(self, sender): if self.current_index == 0: return self.current_index-=1 self.textview.text = self.history[self.current_index-1] def textview_should_begin_editing(self, textview): return True def textview_did_change(self, textview): self.append(textview.text) class Main(ui.View): def __init__(self, *args, **kwargs): text_file=None # Main Window self.width = 650 self.height = 1000 self.background_color="#a18967" self.update_interval=1 self.tv = ui.TextView( frame=(25, 50, 600, 875), background_color='#daba8b') self.file_picker=file_picker_dialog( self, x=self.width/2-250, y=self.height/2-250, file_pattern=['.txt', '.md', '.py']) def file_view(self): self.add_subview( FilePicker(frame=(50,100, self.width-100, self.height-200))) def open_file(self, child_view, file): self.add_subview(self.tv) self.tv.delegate=Text_Editor(self, self.tv, file) with open(file, 'r') as f: for line in f: self.tv.text+=line if __name__ == '__main__': Main().present('sheet', hide_title_bar=True)
-
@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make. -
@Bumbo-Cactoni said:
@stephen
Yes, I am going to attempt to implement it in an app I am going to try to make.since ill probably play with it a little bit we can co-op a bit on it if you like