Source code for editor

"""Text editor class for your favourite editor."""
#
# (C) Pywikibot team, 2004-2022
#
# Distributed under the terms of the MIT license.
#
import codecs
import os
import subprocess
import tempfile
from sys import platform
from textwrap import fill
from typing import Optional

import pywikibot
from pywikibot import config
from pywikibot.backports import List, Sequence


try:
    from pywikibot.userinterfaces import gui  # noqa
    GUI_ERROR = None
except ImportError as e:
    GUI_ERROR = e


[docs]class TextEditor: """Text editor.""" @staticmethod def _command(file_name: str, text: str, jump_index: Optional[int] = None) -> List[str]: """Return editor selected in user config file (user-config.py).""" if jump_index: # Some editors make it possible to mark occurrences of substrings, # or to jump to the line of the first occurrence. # TODO: Find a better solution than hardcoding these, e.g. a config # option. line = text[:jump_index].count('\n') column = jump_index - (text[:jump_index].rfind('\n') + 1) else: line = column = 0 # Linux editors. We use startswith() because some users might use # parameters. assert config.editor is not None if config.editor.startswith('kate'): command = ['-l', str(line + 1), '-c', str(column + 1)] elif config.editor.startswith(('gedit', 'emacs')): command = [f'+{line + 1}'] # columns seem unsupported elif config.editor.startswith('jedit'): command = [f'+line:{line + 1}'] # columns seem unsupported elif config.editor.startswith('vim'): command = [f'+{line + 1}'] # columns seem unsupported elif config.editor.startswith('nano'): command = [f'+{line + 1},{column + 1}'] # Windows editors elif config.editor.lower().endswith('notepad++.exe'): command = [f'-n{line + 1}'] # seems not to support columns else: command = [] # See T102465 for problems relating to using config.editor unparsed. command = [config.editor] + command + [file_name] pywikibot.log(f'Running editor: {TextEditor._concat(command)}') return command @staticmethod def _concat(command: Sequence[str]) -> str: return ' '.join(f'{part!r}' if ' ' in part else part for part in command)
[docs] def edit(self, text: str, jumpIndex: Optional[int] = None, highlight: Optional[str] = None) -> Optional[str]: """ Call the editor and thus allows the user to change the text. Halts the thread's operation until the editor is closed. :param text: the text to be edited :param jumpIndex: position at which to put the caret :param highlight: each occurrence of this substring will be highlighted :return: the modified text, or None if the user didn't save the text file in his text editor """ if config.editor: handle, tempFilename = tempfile.mkstemp() tempFilename = '{}.{}'.format(tempFilename, config.editor_filename_extension) try: with codecs.open(tempFilename, 'w', encoding=config.editor_encoding) as tempFile: tempFile.write(text) creationDate = os.stat(tempFilename).st_mtime cmd = self._command(tempFilename, text, jumpIndex) subprocess.run(cmd, shell=platform == 'win32', check=True) lastChangeDate = os.stat(tempFilename).st_mtime if lastChangeDate == creationDate: # Nothing changed return None with codecs.open(tempFilename, 'r', encoding=config.editor_encoding) as temp_file: newcontent = temp_file.read() return newcontent finally: os.close(handle) os.unlink(tempFilename) if GUI_ERROR: raise ImportError(fill( 'Could not load GUI modules: {}. No editor available. ' 'Set your favourite editor in user-config.py "editor", ' 'or install python packages tkinter and idlelib, which ' 'are typically part of Python but may be packaged separately ' 'on your platform.'.format(GUI_ERROR)) + '\n') assert pywikibot.ui is not None return pywikibot.ui.editText(text, jumpIndex=jumpIndex, highlight=highlight)