"""Text editor class for your favourite editor... note:: This module uses :mod:`userinterfaces.gui` and has depedencies from other partially external modules."""## (C) Pywikibot team, 2004-2023## Distributed under the terms of the MIT license.#from__future__importannotationsimportosimportshleximportsubprocessimporttempfilefrompathlibimportPathfromsysimportplatformfromtextwrapimportfillimportpywikibotfrompywikibotimportconfigfrompywikibot.backportsimportSequencetry:frompywikibot.userinterfacesimportgui# noqa: F401GUI_ERROR=NoneexceptImportErrorase:GUI_ERROR=eOSWIN32=platform=='win32'ifOSWIN32:importwinreg
[docs]classTextEditor:"""Text editor. .. versionchanged:: 8.0 Editor detection functions were moved from :mod:`config`. """def__init__(self):"""Setup external Editor."""self.editor:strifconfig.editorisTrue:self.editor=''elifconfig.editorisFalse:self.editor='break'ifOSWIN32else'true'elifconfig.editorisNone:self.editor=os.environ.get('EDITOR','')ifOSWIN32andnotself.editor:self.editor=self._detect_win32_editor()else:self.editor=config.editordef_command(self,file_name:str,text:str,jump_index:int|None=None)->list[str]:"""Return command of editor selected in user config file."""ifjump_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.ifself.editor.startswith('kate'):command=['-l',str(line+1),'-c',str(column+1)]elifself.editor.startswith(('gedit','emacs')):command=[f'+{line+1}']# columns seem unsupportedelifself.editor.startswith('jedit'):command=[f'+line:{line+1}']# columns seem unsupportedelifself.editor.startswith('vim'):command=[f'+{line+1}']# columns seem unsupportedelifself.editor.startswith('nano'):command=[f'+{line+1},{column+1}']# Windows editorselifself.editor.lower().endswith('notepad++.exe'):command=[f'-n{line+1}']# seems not to support columnselse:command=[]# See T102465 for problems relating to using self.editor unparsed.editor_cmd=[self.editor]ifOSWIN32elseshlex.split(self.editor)command=editor_cmd+command+[file_name]pywikibot.log(f'Running editor: {self._concat(command)}')returncommand@staticmethoddef_concat(command:Sequence[str])->str:return' '.join(f'{part!r}'if' 'inpartelsepartforpartincommand)
[docs]defedit(self,text:str,jumpIndex:int|None=None,highlight:str|None=None)->str|None:""" 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 """ifself.editor:handle,filename=tempfile.mkstemp(suffix=f'.{config.editor_filename_extension}',text=True)path=Path(filename)try:encoding=config.editor_encodingpath.write_text(text,encoding=encoding)creation_date=path.stat().st_mtimecmd=self._command(filename,text,jumpIndex)subprocess.run(cmd,shell=platform=='win32',check=True)last_change_date=path.stat().st_mtimeiflast_change_date==creation_date:returnNone# Nothing changedreturnpath.read_text(encoding=encoding)finally:os.close(handle)os.unlink(path)ifGUI_ERROR:raiseImportError(fill(f'Could not load GUI modules: {GUI_ERROR}. 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.')+'\n')assertpywikibot.uiisnotNonereturnpywikibot.ui.editText(text,jumpIndex=jumpIndex,highlight=highlight)
@staticmethoddef_win32_extension_command(extension:str)->str|None:"""Get the command from the Win32 registry for an extension."""fileexts_key= \
r'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts'key_name=fr'{fileexts_key}\.{extension}\OpenWithProgids'try:key1=winreg.OpenKey(winreg.HKEY_CURRENT_USER,key_name)_prog_id=winreg.EnumValue(key1,0)[0]_key2=winreg.OpenKey(winreg.HKEY_CLASSES_ROOT,fr'{_prog_id}\shell\open\command')_cmd=winreg.QueryValueEx(_key2,'')[0]# See T102465 for issues relating to using this value.cmd=_cmdifcmd.find('%1'):cmd=cmd[:cmd.find('%1')]# Remove any trailing character, which should be a quote or# space and then remove all whitespace.returncmd[:-1].strip()exceptOSErrorase:# Catch any key lookup errorspywikibot.info(f'Unable to detect program for file extension 'f'{extension!r}: {e!r}')returnNone@staticmethoddef_detect_win32_editor()->str:"""Detect the best Win32 editor."""# Notepad is even worse than our Tkinter editor.unusable_exes=['notepad.exe','py.exe','pyw.exe','python.exe','pythonw.exe']forextin['py','txt']:editor=TextEditor._win32_extension_command(ext)ifeditor:forunusableinunusable_exes:ifunusableineditor.lower():breakelse:ifset(editor)&set('\a\b\f\n\r\t\v'):# single character string literals from# https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals# encode('unicode-escape') also changes Unicode# characterspywikibot.warning(fill('The editor path contains probably invalid ''escaped characters. Make sure to use a ''raw-string (r"..." or r\'...\'), forward slashes ''as a path delimiter or to escape the normal path ''delimiter.'))returneditorreturn''