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:
@@ -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,
|
||||
|
||||
6
scripts/presets/text_editor/internal.py
Normal file
6
scripts/presets/text_editor/internal.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import bpy
|
||||
|
||||
filepaths = bpy.context.preferences.filepaths
|
||||
|
||||
filepaths.text_editor = ""
|
||||
filepaths.text_editor_args = ""
|
||||
12
scripts/presets/text_editor/visual_studio_code.py
Normal file
12
scripts/presets/text_editor/visual_studio_code.py
Normal 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"
|
||||
@@ -28,6 +28,7 @@ _modules = [
|
||||
"screen_play_rendered_anim",
|
||||
"sequencer",
|
||||
"spreadsheet",
|
||||
"text",
|
||||
"userpref",
|
||||
"uvcalc_follow_active",
|
||||
"uvcalc_lightmap",
|
||||
|
||||
@@ -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,
|
||||
|
||||
74
scripts/startup/bl_operators/text.py
Normal file
74
scripts/startup/bl_operators/text.py
Normal 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,
|
||||
)
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user