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

@@ -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,