Fix #112377: Edit Source missing info in the status bar

Reports from the internal operator weren't forwarded to the Python
operator, they were printed in the console instead.

Resolve by moving the operator to C++, use a utility function
to launch the external editor instead of an operator.
This commit is contained in:
Campbell Barton
2023-10-05 13:07:59 +11:00
parent c9130e38e3
commit 9d4dc5376c
7 changed files with 157 additions and 107 deletions

View File

@@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
__all__ = (
"open_external_editor"
)
def open_external_editor(filepath, line, column, /):
# Internal Python implementation for `TEXT_OT_jump_to_file_at_point`.
# Returning a non-empty string represents an error, an empty string for success.
import shlex
import subprocess
from string import Template
from bpy import context
text_editor = context.preferences.filepaths.text_editor
text_editor_args = context.preferences.filepaths.text_editor_args
# The caller should check this.
assert text_editor
if not text_editor_args:
return (
"Provide text editor argument format in File Paths/Applications Preferences, "
"see input field tool-tip for more information",
)
if "$filepath" not in text_editor_args:
return "Text Editor Args Format must contain $filepath"
args = [text_editor]
template_vars = {
"filepath": filepath,
"line": line + 1,
"column": column + 1,
"line0": line,
"column0": column,
}
try:
args.extend([Template(arg).substitute(**template_vars) for arg in shlex.split(text_editor_args)])
except BaseException as ex:
return "Exception parsing template: %r" % ex
try:
# With `check=True` if `process.returncode != 0` an exception will be raised.
subprocess.run(args, check=True)
except BaseException as ex:
return "Exception running external editor: %r" % ex
return ""

View File

@@ -31,7 +31,6 @@ _modules = [
"screen_play_rendered_anim",
"sequencer",
"spreadsheet",
"text",
"userpref",
"uvcalc_follow_active",
"uvcalc_lightmap",

View File

@@ -1,85 +0,0 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from bpy.props import (
IntProperty,
StringProperty,
)
class TEXT_OT_jump_to_file_at_point(Operator):
bl_idname = "text.jump_to_file_at_point"
bl_label = "Open Text File at point"
bl_description = "Edit text file in external text editor"
filepath: StringProperty(name="filepath")
line: IntProperty(name="line")
column: IntProperty(name="column")
def execute(self, context):
import shlex
import subprocess
from string import Template
if not self.properties.is_property_set("filepath"):
text = getattr(getattr(context, "space_data", None), "text", None)
if not text:
return {'CANCELLED'}
self.filepath = text.filepath
self.line = text.current_line_index
self.column = text.current_character
text_editor = context.preferences.filepaths.text_editor
text_editor_args = context.preferences.filepaths.text_editor_args
# Use the internal text editor.
if not text_editor:
return bpy.ops.text.jump_to_file_at_point_internal(
filepath=self.filepath,
line=self.line,
column=self.column,
)
if not text_editor_args:
self.report(
{'ERROR_INVALID_INPUT'},
"Provide text editor argument format in File Paths/Applications Preferences, "
"see input field tool-tip for more information",
)
return {'CANCELLED'}
if "$filepath" not in text_editor_args:
self.report({'ERROR_INVALID_INPUT'}, "Text Editor Args Format must contain $filepath")
return {'CANCELLED'}
args = [text_editor]
template_vars = {
"filepath": self.filepath,
"line": self.line + 1,
"column": self.column + 1,
"line0": self.line,
"column0": self.column,
}
try:
args.extend([Template(arg).substitute(**template_vars) for arg in shlex.split(text_editor_args)])
except BaseException as ex:
self.report({'ERROR'}, "Exception parsing template: %r" % ex)
return {'CANCELLED'}
try:
# With `check=True` if `process.returncode != 0` an exception will be raised.
subprocess.run(args, check=True)
except BaseException as ex:
self.report({'ERROR'}, "Exception running external editor: %r" % ex)
return {'CANCELLED'}
return {'FINISHED'}
classes = (
TEXT_OT_jump_to_file_at_point,
)

View File

@@ -206,7 +206,7 @@ static void text_operatortypes()
WM_operatortype_append(TEXT_OT_replace_set_selected);
WM_operatortype_append(TEXT_OT_start_find);
WM_operatortype_append(TEXT_OT_jump_to_file_at_point_internal);
WM_operatortype_append(TEXT_OT_jump_to_file_at_point);
WM_operatortype_append(TEXT_OT_to_3d_object);

View File

@@ -152,7 +152,7 @@ void TEXT_OT_find(wmOperatorType *ot);
void TEXT_OT_find_set_selected(wmOperatorType *ot);
void TEXT_OT_replace(wmOperatorType *ot);
void TEXT_OT_replace_set_selected(wmOperatorType *ot);
void TEXT_OT_jump_to_file_at_point_internal(wmOperatorType *ot);
void TEXT_OT_jump_to_file_at_point(wmOperatorType *ot);
/* text_find = open properties, activate search button */
void TEXT_OT_start_find(wmOperatorType *ot);

View File

@@ -8,6 +8,7 @@
#include <cerrno>
#include <cstring>
#include <sstream>
#include "MEM_guardedalloc.h"
@@ -3987,19 +3988,58 @@ void TEXT_OT_replace_set_selected(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Jump to File at Point (Internal)
*
* \note This is the internal implementation, typically `TEXT_OT_jump_to_file_at_point`
* should be used because it respects the "External Editor" preference.
/** \name Jump to File at Point
* \{ */
static int text_jump_to_file_at_point_internal_exec(bContext *C, wmOperator *op)
static bool text_jump_to_file_at_point_external(bContext *C,
ReportList *reports,
const char *filepath,
const int line_index,
const int column_index)
{
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
const int line = RNA_int_get(op->ptr, "line");
const int column = RNA_int_get(op->ptr, "column");
bool success = false;
#ifdef WITH_PYTHON
BPy_RunErrInfo err_info = {};
err_info.reports = reports;
err_info.report_prefix = "External editor";
const char *expr_imports[] = {"bl_text_utils", "bl_text_utils.external_editor", "os", nullptr};
std::string expr;
{
std::stringstream expr_stream;
expr_stream << "bl_text_utils.external_editor.open_external_editor(os.fsdecode(b'";
for (const char *ch = filepath; *ch; ch++) {
expr_stream << "\\x" << std::hex << int(*ch);
}
expr_stream << "'), " << std::dec << line_index << ", " << std::dec << column_index << ")";
expr = expr_stream.str();
}
char *expr_result = nullptr;
if (BPY_run_string_as_string(C, expr_imports, expr.c_str(), &err_info, &expr_result)) {
/* No error. */
if (expr_result[0] == '\0') {
BKE_reportf(
reports, RPT_INFO, "See '%s' in the external editor", BLI_path_basename(filepath));
success = true;
}
else {
BKE_report(reports, RPT_ERROR, expr_result);
}
MEM_freeN(expr_result);
}
#else
UNUSED_VARS(C, reports, filepath, line_index, column_index);
#endif /* WITH_PYTHON */
return success;
}
static bool text_jump_to_file_at_point_internal(bContext *C,
ReportList *reports,
const char *filepath,
const int line_index,
const int column_index)
{
Main *bmain = CTX_data_main(C);
Text *text = nullptr;
@@ -4015,34 +4055,77 @@ static int text_jump_to_file_at_point_internal_exec(bContext *C, wmOperator *op)
}
if (text == nullptr) {
BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath);
return OPERATOR_CANCELLED;
BKE_reportf(reports, RPT_WARNING, "File '%s' cannot be opened", filepath);
return false;
}
txt_move_to(text, line, column, false);
txt_move_to(text, line_index, column_index, false);
/* naughty!, find text area to set, not good behavior
* but since this is a developer tool lets allow it - campbell */
if (!ED_text_activate_in_screen(C, text)) {
BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2);
BKE_reportf(reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2);
}
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
return OPERATOR_FINISHED;
return true;
}
void TEXT_OT_jump_to_file_at_point_internal(wmOperatorType *ot)
static int text_jump_to_file_at_point_exec(bContext *C, wmOperator *op)
{
PropertyRNA *prop_filepath = RNA_struct_find_property(op->ptr, "filepath");
PropertyRNA *prop_line = RNA_struct_find_property(op->ptr, "line");
PropertyRNA *prop_column = RNA_struct_find_property(op->ptr, "column");
if (!RNA_property_is_set(op->ptr, prop_filepath)) {
if (const Text *text = CTX_data_edit_text(C)) {
if (text->filepath != nullptr) {
const TextLine *line = text->curl;
const int line_index = BLI_findindex(&text->lines, text->curl);
const int column_index = BLI_str_utf8_offset_to_index(line->line, line->len, text->curc);
RNA_property_string_set(op->ptr, prop_filepath, text->filepath);
RNA_property_int_set(op->ptr, prop_line, line_index);
RNA_property_int_set(op->ptr, prop_column, column_index);
}
}
}
char filepath[FILE_MAX];
RNA_property_string_get(op->ptr, prop_filepath, filepath);
const int line_index = RNA_property_int_get(op->ptr, prop_line);
const int column_index = RNA_property_int_get(op->ptr, prop_column);
if (filepath[0] == '\0') {
BKE_report(op->reports, RPT_WARNING, "File path property not set");
return OPERATOR_CANCELLED;
}
bool success;
if (U.text_editor[0] != '\0') {
success = text_jump_to_file_at_point_external(
C, op->reports, filepath, line_index, column_index);
}
else {
success = text_jump_to_file_at_point_internal(
C, op->reports, filepath, line_index, column_index);
}
return success ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void TEXT_OT_jump_to_file_at_point(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Jump to File at Point (Internal)";
ot->idname = "TEXT_OT_jump_to_file_at_point_internal";
ot->description = "Jump to a file for the internal text editor";
ot->name = "Jump to File at Point";
ot->idname = "TEXT_OT_jump_to_file_at_point";
ot->description = "Jump to a file for the text editor";
/* api callbacks */
ot->exec = text_jump_to_file_at_point_internal_exec;
ot->exec = text_jump_to_file_at_point_exec;
/* flags */
ot->flag = 0;