UI: Add a custom text editor preference

Add a user preference to set up a custom text editor for editing text
files with the "Edit Source" action in the UI context menu.

- An operator TEXT_OT_jump_to_file_at_point has been added.
- A custom editor can be set in the user preferences.
- A preset has been included for "Visual Studio Code".
- When the editor is not set, use Blender's internal editor.

Ref !108299.
This commit is contained in:
guishe
2023-05-25 16:25:46 -06:00
committed by Campbell Barton
parent 3b634d6f7f
commit e16ec95a16
11 changed files with 191 additions and 3 deletions

View File

@@ -38,6 +38,8 @@ const UserDef U_default = {
.sounddir = "//",
.i18ndir = "",
.image_editor = "",
.text_editor = "",
.text_editor_args = "",
.anim_player = "",
.anim_player_preset = 0,
.v2d_min_gridsize = 45,

View File

@@ -0,0 +1,6 @@
import bpy
filepaths = bpy.context.preferences.filepaths
filepaths.text_editor = ""
filepaths.text_editor_args = ""

View File

@@ -0,0 +1,12 @@
import bpy
import platform
filepaths = bpy.context.preferences.filepaths
filepaths.text_editor_args = "-g $filepath:$line:$column"
match platform.system():
case "Windows":
filepaths.text_editor = "code.cmd"
case _:
filepaths.text_editor = "code"

View File

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

View File

@@ -413,6 +413,24 @@ class AddPresetHairDynamics(AddPresetBase, Operator):
]
class AddPresetTextEditor(AddPresetBase, Operator):
"""Add or remove a Text Editor Preset"""
bl_idname = "text_editor.preset_add"
bl_label = "Add Text Editor Preset"
preset_menu = "USERPREF_PT_text_editor_presets"
preset_defines = [
"filepaths = bpy.context.preferences.filepaths"
]
preset_values = [
"filepaths.text_editor",
"filepaths.text_editor_args"
]
preset_subdir = "text_editor"
class AddPresetTrackingCamera(AddPresetBase, Operator):
"""Add or remove a Tracking Camera Intrinsics Preset"""
bl_idname = "clip.camera_preset_add"
@@ -692,6 +710,7 @@ classes = (
AddPresetOperator,
AddPresetRender,
AddPresetCameraSafeAreas,
AddPresetTextEditor,
AddPresetTrackingCamera,
AddPresetTrackingSettings,
AddPresetTrackingTrackColor,

View File

@@ -0,0 +1,74 @@
# SPDX-License-Identifier: GPL-2.0-or-later
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 = context.space_data.text
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
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 Exception 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 Exception 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

@@ -256,7 +256,13 @@ class TEXT_MT_text(Menu):
if text:
layout.separator()
layout.operator("text.reload")
row = layout.row()
row.operator("text.reload")
row.enabled = not text.is_in_memory
row = layout.row()
op = row.operator("text.jump_to_file_at_point", text="Edit Externally")
row.enabled = (not text.is_in_memory and context.preferences.filepaths.text_editor != "")
layout.separator()
layout.operator("text.save", icon='FILE_TICK')

View File

@@ -10,6 +10,7 @@ from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
)
from bl_ui.utils import PresetPanel
# -----------------------------------------------------------------------------
@@ -1399,6 +1400,13 @@ class USERPREF_PT_file_paths_render(FilePathsPanel, Panel):
col.prop(paths, "render_cache_directory", text="Render Cache")
class USERPREF_PT_text_editor_presets(PresetPanel, Panel):
bl_label = "Text Editor Presets"
preset_subdir = "text_editor"
preset_operator = "script.execute_preset"
preset_add_operator = "text_editor.preset_add"
class USERPREF_PT_file_paths_applications(FilePathsPanel, Panel):
bl_label = "Applications"
@@ -1416,6 +1424,25 @@ class USERPREF_PT_file_paths_applications(FilePathsPanel, Panel):
col.prop(paths, "animation_player", text="Player")
class USERPREF_PT_text_editor(FilePathsPanel, Panel):
bl_label = "Text Editor"
bl_parent_id = "USERPREF_PT_file_paths_applications"
def draw_header_preset(self, _context):
USERPREF_PT_text_editor_presets.draw_panel_header(self.layout)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
paths = context.preferences.filepaths
col = layout.column()
col.prop(paths, "text_editor", text="Program")
col.prop(paths, "text_editor_args", text="Arguments")
class USERPREF_PT_file_paths_development(FilePathsPanel, Panel):
bl_label = "Development"
@@ -2510,6 +2537,8 @@ classes = (
USERPREF_PT_file_paths_script_directories,
USERPREF_PT_file_paths_render,
USERPREF_PT_file_paths_applications,
USERPREF_PT_text_editor,
USERPREF_PT_text_editor_presets,
USERPREF_PT_file_paths_development,
USERPREF_PT_file_paths_asset_libraries,

View File

@@ -1746,9 +1746,22 @@ static int editsource_text_edit(bContext *C,
Main *bmain = CTX_data_main(C);
Text *text = nullptr;
/* Developers may wish to copy-paste to an external editor. */
printf("%s:%d\n", filepath, line);
if (U.text_editor[0] != '\0') {
wmOperatorType *ot = WM_operatortype_find("TEXT_OT_jump_to_file_at_point", true);
PointerRNA op_props;
WM_operator_properties_create_ptr(&op_props, ot);
RNA_string_set(&op_props, "filepath", filepath);
RNA_int_set(&op_props, "line", line - 1);
RNA_int_set(&op_props, "column", 0);
int result = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props, NULL);
WM_operator_properties_free(&op_props);
if (result & OPERATOR_FINISHED) {
return OPERATOR_FINISHED;
}
}
LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) {
if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) {
text = text_iter;

View File

@@ -729,6 +729,9 @@ typedef struct UserDef {
/** 1024 = FILE_MAX. */
char image_editor[1024];
/** 1024 = FILE_MAX. */
char text_editor[1024];
char text_editor_args[256];
/** 1024 = FILE_MAX. */
char anim_player[1024];
int anim_player_preset;

View File

@@ -6507,6 +6507,29 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_property_string_sdna(prop, NULL, "image_editor");
RNA_def_property_ui_text(prop, "Image Editor", "Path to an image editor");
prop = RNA_def_property(srna, "text_editor", PROP_STRING, PROP_FILEPATH);
RNA_def_property_string_sdna(prop, NULL, "text_editor");
RNA_def_property_ui_text(prop,
"Text Editor",
"Command to launch the text editor, "
"either a full path or a command in $PATH.\n"
"Use the internal editor when left blank");
prop = RNA_def_property(srna, "text_editor_args", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "text_editor_args");
RNA_def_property_ui_text(
prop,
"Text Editor Args",
"Defines the specific format of the arguments with which the text editor opens files. "
"The supported expansions are as follows:\n"
"\n"
"$filepath The absolute path of the file.\n"
"$line The line to open at (Optional).\n"
"$column The column to open from the beginning of the line (Optional).\n"
"$line0 & column0 start at zero."
"\n"
"Example: -f $filepath -l $line -c $column");
prop = RNA_def_property(srna, "animation_player", PROP_STRING, PROP_FILEPATH);
RNA_def_property_string_sdna(prop, NULL, "anim_player");
RNA_def_property_ui_text(