Source code for coalib.results.result_actions.OpenEditorAction

import logging
import shlex
import subprocess
from os.path import exists
from os import environ

from coalib.results.Diff import Diff
from coalib.results.Result import Result
from coalib.results.result_actions.ResultAction import ResultAction
from coala_utils.decorators import enforce_signature
from coala_utils.FileUtils import detect_encoding


"""
Data about all text editors coala knows about. New editors
can just be added here.
For each editor the following info is stored:
{
    <name/comand>: {
        "file_arg_template":
            A string used to generate arguments to open a file.
            Must at least have the placeholder 'filename'
            and can optionally use 'line' and 'column'
            to open the file at the correct position.
            Some editors don't support opening files at
            a certain position if multiple files are
            to be opened, but we try to do so anyway.
        "args":
            General arguments added to the call, e.g. to
            force opening of a new window.
        "gui":
            Boolean. True if this is a gui editor.
            Optional, defaults to False.
    }
}
"""
KNOWN_EDITORS = {
    # non-gui editors
    'vim': {
        'file_arg_template': '{filename} +{line}',
        'gui': False
    },
    'nvim': {
        'file_arg_template': '{filename} +{line}',
        'gui': False
    },
    'nano': {
        'file_arg_template': '+{line},{column} {filename} ',
        'gui': False
    },
    'emacs': {
        'file_arg_template': '+{line}:{column} {filename}',
        'gui': False
    },
    'emacsclient': {
        'file_arg_template': '+{line}:{column} {filename}',
        'gui': False
    },

    # gui editors
    'atom': {
        'file_arg_template': '{filename}:{line}:{column}',
        'args': '--wait',
        'gui': True
    },
    'geany': {
        'file_arg_template': '{filename} -l {line} --column {column}',
        'args': '-s -i',
        'gui': True
    },
    'gedit': {
        'file_arg_template': '{filename} +{line}',
        'args': '-s',
        'gui': True
    },
    'gvim': {
        'file_arg_template': '{filename} +{line}',
        'gui': True
    },
    'kate': {
        'file_arg_template': '{filename} -l {line} -c {column}',
        'args': '--new',
        'gui': True
    },
    'notepadqq': {
        'file_arg_template': '{filename}',
        'gui': True
    },
    'subl': {
        'file_arg_template': '{filename}:{line}:{column}',
        'args': '--wait',
        'gui': True
    },
    'xed': {
        'file_arg_template': '{filename} +{line}',
        'args': '--new-window',
        'gui': True
    },
}


[docs]class OpenEditorAction(ResultAction): SUCCESS_MESSAGE = 'Changes saved successfully.'
[docs] @staticmethod @enforce_signature def is_applicable(result: Result, original_file_dict, file_diff_dict, applied_actions=()): """ For being applicable, the result has to point to a number of files that have to exist i.e. have not been previously deleted. """ if not len(result.affected_code) > 0: return 'The result is not associated with any source code.' filenames = set(src.renamed_file(file_diff_dict) for src in result.affected_code) if not all(exists(filename) for filename in filenames): return ("The result is associated with source code that doesn't " 'seem to exist.') return True
[docs] def build_editor_call_args(self, editor, editor_info, filenames): """ Create argument list which will then be used to open an editor for the given files at the correct positions, if applicable. :param editor: The editor to open the file with. :param editor_info: A dict containing the keys ``args`` and ``file_arg_template``, providing additional call arguments and a template to open files at a position for this editor. :param filenames: A dict holding one entry for each file to be opened. Keys must be ``filename``, ``line`` and ``column``. """ call_args = [editor] # for some editors we define extra arguments if 'args' in editor_info: call_args += shlex.split(editor_info['args']) # add info for each file to be opened for file_info in filenames.values(): file_arg = editor_info['file_arg_template'].format( filename=shlex.quote(file_info['filename']), line=file_info['line'], column=file_info['column'] ) call_args += shlex.split(file_arg) return call_args
[docs] def apply(self, result, original_file_dict, file_diff_dict, editor: str): """ (O)pen file :param editor: The editor to open the file with. """ try: editor_info = KNOWN_EDITORS[editor.strip()] except KeyError: # If the editor is unknown fall back to just passing # the filenames and emit a warning logging.warning( 'The editor "{editor}" is unknown to coala. Files won\'t be' ' opened at the correct positions and other quirks might' ' occur. Consider opening an issue at' ' https://github.com/coala/coala/issues so we' ' can add support for this editor.' ' Supported editors are: {supported}'.format( editor=editor, supported=', '.join( sorted(KNOWN_EDITORS.keys()) ) ) ) editor_info = { 'file_arg_template': '{filename}', 'gui': False } # Use dict to remove duplicates filenames = { src.file: { 'filename': src.renamed_file(file_diff_dict), 'line': src.start.line or 1, 'column': src.start.column or 1 } for src in result.affected_code } call_args = self.build_editor_call_args(editor, editor_info, filenames) if editor_info.get('gui', True): subprocess.call(call_args, stdout=subprocess.PIPE) else: subprocess.call(call_args) for original_name, file_info in filenames.items(): filename = file_info['filename'] with open(filename, encoding=detect_encoding(filename)) as file: file_diff_dict[original_name] = Diff.from_string_arrays( original_file_dict[original_name], file.readlines(), rename=False if original_name == filename else filename) return file_diff_dict
if 'EDITOR' in environ: apply.__defaults__ = (environ['EDITOR'],)