| # SPDX-License-Identifier: GPL-2.0 | |
| # text viewer | |
| import os | |
| import subprocess | |
| import _hkml_cli | |
| import _hkml_subproc | |
| import hkml_list | |
| import hkml_mail_note | |
| import hkml_open | |
| import hkml_view | |
| import hkml_view_mails | |
| import hkml_write | |
| class TextViewData: | |
| mail = None | |
| mails_slist = None # ScrollableList of parent mails list | |
| unfolded_lines = None | |
| mail_item = None | |
| def __init__(self, mail, mails_slist=None, mail_item=None): | |
| self.mail = mail | |
| self.mails_slist = mails_slist | |
| self.mail_item = mail_item | |
| def menu_exec_git(slist, answer, selection): | |
| hkml_view.shell_mode_end(slist) | |
| git_cmd = selection.data | |
| words = git_cmd.split() | |
| try: | |
| output = subprocess.check_output( | |
| words, stderr=subprocess.DEVNULL).decode().split('\n') | |
| except Exception as e: | |
| output = ['failed: %s' % e, '', | |
| 'wrong commit id, or you are not on the git repo?'] | |
| show_text_viewer(slist.screen, args=ShowTextViewerArgs(text_lines=output)) | |
| hkml_view.shell_mode_start(slist) | |
| def menu_hkml_thread(slist, answer, selection): | |
| hkml_view.shell_mode_end(slist) | |
| msgid = selection.data | |
| args = hkml_view_mails.hkml_list_args_for_msgid(msgid) | |
| hkml_view_mails.gen_show_mails_list(slist.screen, args) | |
| hkml_view.shell_mode_start(slist) | |
| def menu_hkml_open(slist, answer, selection): | |
| hkml_view.shell_mode_end(slist) | |
| msgid = selection.data | |
| args = hkml_view_mails.hkml_list_args_for_msgid(msgid) | |
| args.stdout = True # to bypass dim_old suggestion | |
| # do not suggest manifest update, since this is for msgid. | |
| list_data, err = hkml_list.args_to_mails_list_data( | |
| args, suggest_manifest_update=False) | |
| for line_nr, mail_idx in list_data.line_nr_mail_idx_map.items(): | |
| mail = list_data.mail_items[mail_idx].mail | |
| if mail.get_msgid() == msgid: | |
| _, cols = slist.screen.getmaxyx() | |
| lines = hkml_open.mail_display_str(mail, cols).split('\n') | |
| show_text_viewer(slist.screen, | |
| args=ShowTextViewerArgs(text_lines=lines)) | |
| break | |
| hkml_view.shell_mode_start(slist) | |
| def menu_open_file(slist, answer, selection): | |
| hkml_view.shell_mode_end(slist) | |
| file_path = selection.data | |
| with open(file_path, 'r') as f: | |
| lines = f.read().split('\n') | |
| show_text_viewer(slist.screen, args=ShowTextViewerArgs(text_lines=lines)) | |
| hkml_view.shell_mode_start(slist) | |
| def menu_open_file_editor(slist, answer, selection): | |
| file_path = selection.data | |
| err = hkml_write.open_editor(file_path, 'file') | |
| if err is not None: | |
| print(err) | |
| exit(1) | |
| def is_git_hash(word): | |
| if len(word) < 10: | |
| return False | |
| for c in word: | |
| if c not in '0123456789abcdef': | |
| return False | |
| return True | |
| def menu_selections_for_commit(line): | |
| for separator in [',', '(', ')', '/', '[', ']', '"']: | |
| line = line.replace(separator, ' ') | |
| selections = [] | |
| for word in line.split(): | |
| if not is_git_hash(word): | |
| continue | |
| show_cmd = 'git show %s' % word | |
| log_five_cmd = 'git log -n 5 %s' % word | |
| log_oneline_cmd = 'git log --oneline -n 64 %s' % word | |
| selections.append( | |
| _hkml_cli.Selection( | |
| show_cmd, handle_fn=menu_exec_git, data=show_cmd)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| log_five_cmd, handle_fn=menu_exec_git, data=log_five_cmd)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| log_oneline_cmd, handle_fn=menu_exec_git, | |
| data=log_oneline_cmd)) | |
| return selections | |
| def get_msgid_from_public_inbox_link(word): | |
| ''' | |
| If it is http url and has @ in a field, assume it is msgid link | |
| ''' | |
| if not word.startswith('http'): | |
| return None | |
| tokens = word.split('/') | |
| if len(tokens) < 4: | |
| return None | |
| for token in tokens[3:]: | |
| if '@' in token: | |
| return token | |
| return None | |
| def menu_selections_for_msgid(line): | |
| for separator in [',', '(', ')', '[', ']', '"']: | |
| line = line.replace(separator, ' ') | |
| selections = [] | |
| for word in line.split(): | |
| msgid = get_msgid_from_public_inbox_link(word) | |
| if msgid is None: | |
| continue | |
| selections.append(_hkml_cli.Selection( | |
| 'hkml thread %s' % msgid, handle_fn=menu_hkml_thread, data=msgid)) | |
| selections.append(_hkml_cli.Selection( | |
| 'hkml open %s' % msgid, handle_fn=menu_hkml_open, data=msgid)) | |
| return selections | |
| def menu_exec_web(slist, answer, selection): | |
| cmd = selection.data | |
| subprocess.call(cmd.split()) | |
| def menu_selections_for_url(line): | |
| for separator in [',', '(', ')', '[', ']', '"']: | |
| line = line.replace(separator, ' ') | |
| selections = [] | |
| for word in line.split(): | |
| if not word.startswith('http://') and not word.startswith('https://'): | |
| continue | |
| if _hkml_subproc.cmd_available('lynx'): | |
| cmd = 'lynx %s' % word | |
| selections.append(_hkml_cli.Selection( | |
| cmd, handle_fn=menu_exec_web, data=cmd)) | |
| if _hkml_subproc.cmd_available('w3m'): | |
| cmd = 'w3m %s' % word | |
| selections.append(_hkml_cli.Selection( | |
| cmd, handle_fn=menu_exec_web, data=cmd)) | |
| return selections | |
| def menu_selections_for_files(line): | |
| for separator in [',', '(', ')', '[', ']', '"', ':']: | |
| line = line.replace(separator, ' ') | |
| found_files = {} | |
| selections = [] | |
| for word in line.split(): | |
| # file paths on diff starts with a/ and b/, e.g., | |
| # | |
| # --- a/tools/testing/selftests/damon/damon_nr_regions.py | |
| # +++ b/tools/testing/selftests/damon/damon_nr_regions.py | |
| if word.startswith('a/') or word.startswith('b/'): | |
| word = word[2:] | |
| if not word in found_files and os.path.isfile(word): | |
| found_files[word] = True | |
| selections.append(_hkml_cli.Selection( | |
| text='hkml open file %s' % word, handle_fn=menu_open_file, | |
| data=word)) | |
| selections.append(_hkml_cli.Selection( | |
| text='open %s with a text editor' % word, | |
| handle_fn=menu_open_file_editor, data=word)) | |
| return selections | |
| def is_showing_mail(slist): | |
| # slist.data should be TextViewData | |
| return slist.data.mail is not None | |
| def get_showing_mail(slist): | |
| # slist.data should be TextViewData | |
| mail = slist.data.mail | |
| if mail is None: | |
| return None, 'not showing mail?' | |
| return mail, None | |
| def get_showing_mail_item(slist): | |
| mail_item = slist.data.mail_item | |
| if mail_item is None: | |
| return None, 'not showing mail item?' | |
| return mail_item, None | |
| def reply_mail(c, slist): | |
| # maybe called from tui/cli menu | |
| if slist.parent_list is not None: | |
| slist = slist.parent_list | |
| mail, err = get_showing_mail(slist) | |
| if err is not None: | |
| slist.toast('parent is not a mail?') | |
| return | |
| nr_header_rows = 0 | |
| for line in slist.lines: | |
| nr_header_rows += 1 | |
| if line == '': | |
| break | |
| hkml_view_mails.reply_mail( | |
| slist, mail, cursor_row=slist.focus_row - nr_header_rows) | |
| def menu_reply_mail(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| # todo: handle err is not None case | |
| hkml_view.shell_mode_end(slist) | |
| hkml_view_mails.reply_mail(slist, mail) | |
| hkml_view.shell_mode_start(slist) | |
| def forward_mail(c, slist): | |
| # maybe called from tui/cli menu | |
| if slist.parent_list is not None: | |
| slist = slist.parent_list | |
| mail, err = get_showing_mail(slist) | |
| if err is not None: | |
| slist.toast('parent is not a mail?') | |
| return | |
| hkml_view_mails.forward_mail(slist, mail) | |
| def menu_forward_mail(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| # todo: handle err is not None case | |
| hkml_view.shell_mode_end(slist) | |
| hkml_view_mails.forward_mail(slist, mail) | |
| hkml_view.shell_mode_start(slist) | |
| def write_draft_mail(c, slist): | |
| # maybe called from tui/cli menu | |
| if slist.parent_list is not None: | |
| slist = slist.parent_list | |
| mail, err = get_showing_mail(slist) | |
| if err is not None: | |
| slist.toast('parent is not a mail?') | |
| return | |
| hkml_view_mails.write_mail_draft(slist, mail) | |
| def menu_write_draft(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| # todo: handle err is not None case | |
| hkml_view.shell_mode_end(slist) | |
| hkml_view_mails.write_mail_draft(slist, mail) | |
| hkml_view.shell_mode_start(slist) | |
| def menu_manage_tags(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| # todo: handle err is not None case | |
| hkml_view_mails.manage_tags_of_mail(slist, mail) | |
| def menu_handle_patches(slist, answer, selection): | |
| mail_item, err = get_showing_mail_item(slist) | |
| # todo: handle err is not None case | |
| hkml_view_mails.handle_patches_of_mail_item(mail_item) | |
| def menu_jump(slist, answer, selection): | |
| selections = [ | |
| _hkml_cli.Selection('beginning of next different depth'), | |
| _hkml_cli.Selection('end of previous different depth')] | |
| answer, selection, err = _hkml_cli.ask_selection( | |
| desc='Select where to jump.', selections=selections, | |
| default_selection_idx=0) | |
| current_depth = mail_depth(slist.lines[slist.focus_row]) | |
| if selection == selections[0]: | |
| for row_idx in range(slist.focus_row + 1, len(slist.lines)): | |
| line = slist.lines[row_idx] | |
| if mail_depth(line) != current_depth: | |
| slist.focus_row = row_idx | |
| return | |
| return | |
| elif selection == selections[1]: | |
| for row_idx in range(slist.focus_row - 1, -1, -1): | |
| line = slist.lines[row_idx] | |
| if mail_depth(line) != current_depth: | |
| slist.focus_row = row_idx | |
| return | |
| return | |
| def menu_add_note(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| if err is not None: | |
| print(err) | |
| return | |
| desc_lines = ['Add note for', ''] | |
| if slist.focus_row > 5: | |
| desc_lines.append('[...]') | |
| desc_lines += slist.lines[slist.focus_row - 5:slist.focus_row + 1] | |
| desc_lines.append('') | |
| note, err = _hkml_cli.ask_input(desc='\n'.join(desc_lines), prompt='Enter') | |
| if err is not None: | |
| print(err) | |
| return | |
| nr_lines_before_body = slist.lines.index('') + 1 | |
| note_line_nr = slist.focus_row - nr_lines_before_body | |
| hkml_mail_note.add_note(mail.get_msgid(), note_line_nr, note) | |
| def menu_remove_notes(slist, answer, selection): | |
| mail, err = get_showing_mail(slist) | |
| if err is not None: | |
| print(err) | |
| return | |
| msgid = mail.get_msgid() | |
| notes = hkml_mail_note.get_notes_for(msgid) | |
| if notes is None: | |
| print('No note for the mail') | |
| return | |
| nr_lines_before_body = slist.lines.index('') + 1 | |
| note_line_nr = slist.focus_row - nr_lines_before_body | |
| if not note_line_nr in notes.line_notes: | |
| print('No note on the line') | |
| return | |
| desc_lines = ['Remove notes for', ''] | |
| if slist.focus_row > 5: | |
| desc_lines.append('[...]') | |
| desc_lines += slist.lines[slist.focus_row - 5:slist.focus_row + 1] | |
| desc_lines.append('') | |
| desc_lines.append('Notes for the line:') | |
| for idx, note in enumerate(notes.line_notes[note_line_nr]): | |
| desc_lines.append('%d: %s' % (idx, note.text)) | |
| desc_lines.append('') | |
| desc = '\n'.join(desc_lines) | |
| prompt='Enter indices of the notes separated by comma, or "all" for all' | |
| answer, err = _hkml_cli.ask_input(desc=desc, prompt=prompt) | |
| if err is not None: | |
| print(err) | |
| return | |
| if answer == 'all': | |
| indices = [i for i in range(len(notes.line_notes[note_line_nr]))] | |
| else: | |
| try: | |
| indices = [int(x) for x in answer.split(',')] | |
| except: | |
| print('wrong input') | |
| return | |
| hkml_mail_note.remove_notes(msgid, note_line_nr, indices) | |
| def menu_selections_for_mail(): | |
| return [ | |
| _hkml_cli.Selection('reply', handle_fn=menu_reply_mail), | |
| _hkml_cli.Selection('forward', handle_fn=menu_forward_mail), | |
| _hkml_cli.Selection( | |
| 'continue draft writing', handle_fn=menu_write_draft), | |
| _hkml_cli.Selection('manage tags', | |
| handle_fn=menu_manage_tags), | |
| _hkml_cli.Selection( | |
| 'handle as patches', handle_fn=menu_handle_patches), | |
| _hkml_cli.Selection( | |
| 'jump cursor to ...', handle_fn=menu_jump), | |
| _hkml_cli.Selection( | |
| 'add a note to the line', handle_fn=menu_add_note), | |
| _hkml_cli.Selection( | |
| 'remove notes of the line', handle_fn=menu_remove_notes), | |
| ] | |
| def menu_wrap_text(slist, answer, selection): | |
| if slist.wrapped_text(): | |
| slist.unwrap_text() | |
| else: | |
| slist.wrap_text() | |
| def menu_save_content_as(slist, answer, selection): | |
| hkml_view.save_as('\n'.join(slist.lines)) | |
| def menu_open_content_with(slist, answer, selection): | |
| err = hkml_view.open_content_with('\n'.join(slist.lines)) | |
| if err is not None: | |
| print(err) | |
| def indent_depth(line): | |
| depth = 0 | |
| for c in line: | |
| if c in [' ', '\t']: | |
| depth += 1 | |
| else: | |
| return depth | |
| return depth | |
| def menu_fold_lines(slist, answer, selection): | |
| focus_row = slist.focus_row | |
| current_depth = indent_depth(slist.lines[focus_row]) | |
| lines_to_fold = 0 | |
| for line in slist.lines[focus_row + 1:]: | |
| if indent_depth(line) > current_depth: | |
| lines_to_fold += 1 | |
| else: | |
| break | |
| text_view_data = slist.data | |
| if text_view_data.unfolded_lines is None: | |
| text_view_data.unfolded_lines = slist.lines | |
| slist.lines = slist.lines[:focus_row + 1] + \ | |
| ['[%d lines folded]' % lines_to_fold] + \ | |
| slist.lines[focus_row + 1 + lines_to_fold:] | |
| def menu_unfold_all_lines(slist, answer, selection): | |
| text_view_data = slist.data | |
| if text_view_data.unfolded_lines is None: | |
| print('No folded lines') | |
| return | |
| slist.lines = text_view_data.unfolded_lines | |
| def menu_selections(slist): | |
| line = slist.lines[slist.focus_row] | |
| selections = menu_selections_for_commit(line) | |
| selections += menu_selections_for_msgid(line) | |
| selections += menu_selections_for_url(line) | |
| selections += menu_selections_for_files(line) | |
| if is_showing_mail(slist): | |
| selections += menu_selections_for_mail() | |
| if slist.wrapped_text(): | |
| menu_text = 'unwrap text' | |
| else: | |
| menu_text = 'wrap text' | |
| selections.append( | |
| _hkml_cli.Selection(menu_text, handle_fn=menu_wrap_text, | |
| data=slist)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| text='save screen content to ...', | |
| handle_fn=menu_save_content_as, data=slist)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| text='open screen content with ...', | |
| handle_fn=menu_open_content_with, data=slist)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| text='fold below indented lines', | |
| handle_fn=menu_fold_lines, data=slist)) | |
| selections.append( | |
| _hkml_cli.Selection( | |
| text='unfold all folded lines', | |
| handle_fn=menu_unfold_all_lines, data=slist)) | |
| return selections | |
| def show_text_viewer_menu(c, slist): | |
| hkml_view.shell_mode_start(slist) | |
| desc_lines = ['selected line: %s' % slist.lines[slist.focus_row]] | |
| text_view_data = slist.data | |
| if text_view_data.mail is not None: | |
| msgid = text_view_data.mail.get_msgid()[1:-1] | |
| desc_lines.append('msgid: %s' % msgid) | |
| desc = '\n'.join(desc_lines) | |
| _hkml_cli.ask_selection( | |
| desc=desc, | |
| prompt='Enter menu item number', | |
| handler_common_data=slist, | |
| selections=menu_selections(slist)) | |
| hkml_view.shell_mode_end(slist) | |
| def get_text_viewer_handlers(data): | |
| if data is not None and data.mail is not None: | |
| handlers = [ | |
| hkml_view.InputHandler(['r'], reply_mail, 'reply'), | |
| hkml_view.InputHandler(['f'], forward_mail, 'forward'), | |
| ] | |
| else: | |
| handlers = [] | |
| return handlers + [ | |
| hkml_view.InputHandler( | |
| ['m'], show_text_viewer_menu, 'open menu'), | |
| ] | |
| def text_color_callback(slist, line_idx): | |
| is_hunk = False | |
| for start, end in slist.hunk_lines: | |
| if start <= line_idx and line_idx < end: | |
| is_hunk = True | |
| break | |
| line = slist.lines[line_idx] | |
| if len(line) == 0: | |
| return hkml_view.normal_color | |
| if is_hunk: | |
| if line[0] == '+': | |
| return hkml_view.add_color | |
| elif line[0] == '-': | |
| return hkml_view.delete_color | |
| elif line.startswith('> > ') or line.startswith('>> '): | |
| return hkml_view.original_color | |
| elif line[0] == '>': | |
| return hkml_view.focus_color | |
| return hkml_view.normal_color | |
| def hunk_length(lines, orig_content, new_content): | |
| length = 0 | |
| for line in lines: | |
| if line.startswith('-'): | |
| orig_content -= 1 | |
| elif line.startswith('+'): | |
| new_content -= 1 | |
| elif line.startswith(' ') or line == '': | |
| orig_content -= 1 | |
| new_content -= 1 | |
| else: | |
| return length | |
| length += 1 | |
| if orig_content < 0 or new_content < 0 or \ | |
| (orig_content == new_content == 0): | |
| return length | |
| return length | |
| def hunk_lines(text_lines): | |
| indices = [] | |
| idx = 0 | |
| while idx < len(text_lines): | |
| line = text_lines[idx] | |
| if not line.startswith('@@'): | |
| idx += 1 | |
| continue | |
| fields = line.split() | |
| # format: "@@ -l,s +l,s @@ optional section heading" | |
| if len(fields) < 4: | |
| idx += 1 | |
| continue | |
| numbers = fields[1].split(',') + fields[2].split(',') | |
| if len(numbers) != 4: | |
| idx += 1 | |
| continue | |
| try: | |
| numbers = [int(x) for x in numbers] | |
| except: | |
| idx += 1 | |
| continue | |
| orig_content, new_content = numbers[1], numbers[3] | |
| hunk_len = hunk_length(text_lines[idx + 1:], orig_content, new_content) | |
| indices.append([idx + 1, idx + 1 + hunk_len]) | |
| idx += hunk_len + 1 | |
| return indices | |
| def mail_depth(line): | |
| depth = 0 | |
| for c in line: | |
| if not c in ['>', ' ']: | |
| break | |
| if c == '>': | |
| depth += 1 | |
| return depth | |
| def is_context_line(line): | |
| # e.g., "On Fri, 2 May 2025 08:49:49 -0700 SeongJae Park <sj@kernel.org> wrote:" | |
| if line.endswith('wrote:'): | |
| return True | |
| # e.g., "* SeongJae Park <sj@kernel.org> [250509 01:47]:" | |
| # todo: maybe need more test...? | |
| if line.endswith(']:'): | |
| return True | |
| return False | |
| def parse_mail_contexts(text_lines): | |
| ''' | |
| Parse which depth origin lines are sent by who, when. | |
| ''' | |
| contexts = {} # key: depth (int), value: context line (strting) | |
| for idx, line in enumerate(text_lines): | |
| if not is_context_line(line): | |
| continue | |
| depth = mail_depth(line) + 1 | |
| if depth in contexts: | |
| continue | |
| contexts[depth] = line | |
| return contexts | |
| def set_mail_contexts(slist): | |
| contexts = parse_mail_contexts(slist.lines) | |
| mail_item = slist.data.mail_item | |
| mail = mail_item.mail | |
| contexts[0] = '%s, %s' % ( | |
| mail.get_from(), mail.get_field('local-date')) | |
| depth = 1 | |
| while mail_item.parent_item is not None: | |
| mail_item = mail_item.parent_item | |
| mail = mail_item.mail | |
| contexts[depth] = '%s, %s' % ( | |
| mail.get_from(), mail.get_field('local-date')) | |
| depth += 1 | |
| slist.data.mail_contexts = contexts | |
| def mail_draw_callback(slist): | |
| focused_line = slist.lines[slist.focus_row] | |
| depth = mail_depth(focused_line) | |
| text_view_data = slist.data | |
| if not depth in text_view_data.mail_contexts: | |
| context = 'unknown' | |
| else: | |
| context = text_view_data.mail_contexts[depth] | |
| slist.bottom_lines = ['# context: %s' % context] | |
| def mail_line_callback(slist, line_idx): | |
| ''' | |
| If the previous line has notes, add those at the beginning of the current | |
| line. | |
| ''' | |
| line = slist.lines[line_idx] | |
| msgid = slist.data.mail.get_msgid() | |
| notes = hkml_mail_note.get_notes_for(msgid) | |
| if notes is None: | |
| return line | |
| body_start_idx = slist.lines.index('') + 1 | |
| prev_body_line_idx = line_idx - 1 - body_start_idx | |
| if prev_body_line_idx in notes.line_notes: | |
| notes_text = ','.join( | |
| [n.text for n in notes.line_notes[prev_body_line_idx]]) | |
| return ' /* hkml note: %s */ %s' % (notes_text, line) | |
| # TODO: handle notes on the last line. | |
| return line | |
| class ShowTextViewerArgs: | |
| text_lines = None | |
| text_view_data = None # TextViewData object | |
| cursor_position = None | |
| def __init__(self, text_lines, text_view_data=None, cursor_position=None): | |
| self.text_lines = text_lines | |
| self.text_view_data = text_view_data | |
| self.cursor_position = cursor_position | |
| def show_text_viewer(screen, args): | |
| ''' | |
| This function can be passed to view() as draw_fn(). | |
| args is a ShowTextViewerArgs object. | |
| ''' | |
| text_lines = args.text_lines | |
| text_view_data = args.text_view_data | |
| cursor_position = args.cursor_position | |
| slist = hkml_view.ScrollableList( | |
| screen, text_lines, get_text_viewer_handlers(text_view_data)) | |
| if text_view_data is None: | |
| text_view_data = TextViewData(mail=None) | |
| slist.data = text_view_data | |
| slist.hunk_lines = hunk_lines(text_lines) | |
| slist.color_callback = text_color_callback | |
| if cursor_position is not None: | |
| slist.focus_row, slist.focus_col = cursor_position | |
| better_wrap, longest_columns = slist.better_wrap_text() | |
| if better_wrap is True and not slist.wrapped_text(): | |
| hkml_view.shell_mode_start(slist) | |
| answer = input('\n'.join([ | |
| 'There are lines that may better to be wrapped (%d columns).' % | |
| longest_columns, | |
| 'May I wrap those?', | |
| 'You can [un]wrap later from the menu (\'m\' key).', | |
| 'Answer: [y/N] '])) | |
| if answer.lower() == 'y': | |
| slist.wrap_text() | |
| print('Ok, wrapped the lines') | |
| else: | |
| print('Ok, kept those as is') | |
| hkml_view.shell_mode_end(slist) | |
| if is_showing_mail(slist): | |
| set_mail_contexts(slist) | |
| slist.draw_callback = mail_draw_callback | |
| slist.line_callback = mail_line_callback | |
| slist.draw() | |
| return slist |