Merge branch 'blender-v4.0-release'

This commit is contained in:
Campbell Barton 2023-10-05 13:16:23 +11:00
commit ed7b7c2cde
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;