« get me outta code hell

dotfiles - Miscellaneous configuration files of my personal use
about summary refs log tree commit diff
path: root/apps/Sublime Text 4/Packages/User
diff options
context:
space:
mode:
Diffstat (limited to 'apps/Sublime Text 4/Packages/User')
-rw-r--r--apps/Sublime Text 4/Packages/User/Default (OSX).sublime-keymap31
-rw-r--r--apps/Sublime Text 4/Packages/User/HSMusic Editing.py222
2 files changed, 253 insertions, 0 deletions
diff --git a/apps/Sublime Text 4/Packages/User/Default (OSX).sublime-keymap b/apps/Sublime Text 4/Packages/User/Default (OSX).sublime-keymap
index 2250e7d..3bb8737 100644
--- a/apps/Sublime Text 4/Packages/User/Default (OSX).sublime-keymap
+++ b/apps/Sublime Text 4/Packages/User/Default (OSX).sublime-keymap
@@ -10,4 +10,35 @@
 			{ "key": "overlay_has_focus", "operator": "equal", "operand": false },
 		],
 	},
+
+	// HSMusic wiki tag editing
+	// (bind caps lock to f16 in Karabiner-Elements)
+
+	{ "keys": ["f16"],
+		"command": "enter_exit_wiki_tag",
+		"context": [
+			{ "key": "project_name", "operator": "regex_contains", "operand": "HSMusic" },
+		],
+	},
+
+	{ "keys": ["shift+f16"],
+		"command": "hyphen_in_wiki_tag",
+		"context": [
+			{ "key": "project_name", "operator": "regex_contains", "operand": "HSMusic" },
+		],
+	},
+
+	{ "keys": [" "],
+		"command": "space_in_wiki_tag",
+		"context": [
+			{ "key": "project_name", "operator": "regex_contains", "operand": "HSMusic" },
+		],
+	},
+
+	{ "keys": ["shift+space"],
+		"command": "space_in_wiki_tag",
+		"context": [
+			{ "key": "project_name", "operator": "regex_contains", "operand": "HSMusic" },
+		],
+	},
 ]
diff --git a/apps/Sublime Text 4/Packages/User/HSMusic Editing.py b/apps/Sublime Text 4/Packages/User/HSMusic Editing.py
new file mode 100644
index 0000000..63e3dc3
--- /dev/null
+++ b/apps/Sublime Text 4/Packages/User/HSMusic Editing.py
@@ -0,0 +1,222 @@
+import sublime
+import sublime_plugin
+import re
+import os
+
+class ProjectSpecificKeyBinding(sublime_plugin.EventListener):
+  def on_query_context(self, view, key, operator, operand, match_all):
+    current_project_name = view.window().project_file_name()
+    if key != "project_name" and os.path.isfile(current_project_name):
+      return None
+
+    if operator == sublime.OP_EQUAL:
+      return current_project_name == operand
+    elif operator == sublime.OP_NOT_EQUAL:
+      return current_project_name != operand
+    elif operator == sublime.QueryOperator.REGEX_CONTAINS:
+      return re.search(operand, current_project_name)
+    elif operator == sublime.QueryOperator.NOT_REGEX_CONTAINS:
+      return not re.search(operand, current_project_name)
+
+class _CursorAdaptiveCommand(sublime_plugin.TextCommand):
+  handle_region_fallback = ('keep',)
+
+  def __align_point_after_insertions(self, orig_point, direction):
+    new_point = orig_point
+    for (insert_point, insert_offset) in self.__inserted_offsets:
+      if direction == 'right' and insert_point <= orig_point:
+        new_point += insert_offset
+      elif direction == 'left' and insert_point < orig_point:
+        new_point += insert_offset
+    return new_point
+
+  def __cleanup(self):
+    self.__inserted_offsets = list()
+
+  def run(self, edit, **kwargs):
+    self.__cleanup()
+
+    actions = list()
+    for region in self.view.sel():
+      action = self.handle_region(region)
+      actions.append((region, *(action or self.handle_region_fallback)))
+
+    new_regions = list()
+    for (region, action, *args) in actions:
+      handler = getattr(self, f'perform_{action}')
+      new_region = handler(edit, region, *args)
+      if new_region:
+        new_regions.append(new_region)
+      else:
+        new_regions.append(self.perform_keep(region, edit))
+
+    real_regions = list()
+    for (region, direction, delta, backoff) in new_regions:
+      if direction == 'collapse-left':
+        if region.a < region.b:
+          point = self.__align_point_after_insertions(region.a, 'right')
+        else:
+          point = self.__align_point_after_insertions(region.b, 'right')
+        real_regions.append(point)
+      elif direction == 'collapse-right':
+        if region.a < region.b:
+          point = self.__align_point_after_insertions(region.a, 'left')
+        else:
+          point = self.__align_point_after_insertions(region.b, 'left')
+        real_regions.append(point)
+      elif direction in ['left', 'right']:
+        start = region.a
+        end = region.b + delta
+        aligned_end = self.__align_point_after_insertions(end, direction)
+        if region.a == region.b:
+          if backoff:
+            if direction == 'right':
+              aligned_end -= backoff
+            else:
+              aligned_end += backoff
+          real_regions.append(sublime.Region(aligned_end, aligned_end))
+        else:
+          aligned_start = self.__align_point_after_insertions(start, direction)
+          real_regions.append(sublime.Region(aligned_start, aligned_end))
+
+    self.view.sel().clear()
+    self.view.sel().add_all(real_regions)
+    self.__cleanup()
+
+  def handle_region(self, region):
+    pass
+
+  def perform_keep(self, edit, region):
+    return (region, 'right', 0, 0)
+
+  def perform_move(self, edit, region, delta):
+    return (region, 'right', delta, 0)
+
+  def perform_insert(self, edit, region, direction, insert, backoff):
+    insert_point = region.b
+    aligned_insert_point = self.__align_point_after_insertions(insert_point, direction)
+    insert_offset = self.view.insert(edit, aligned_insert_point, insert)
+    self.__inserted_offsets.append((insert_point, insert_offset))
+    return (region, direction, 0, backoff)
+
+  def perform_replace(self, edit, region, direction, erase, replace, backoff):
+    replace_point = region.b
+    replace_point -= erase
+    aligned_replace_point = self.__align_point_after_insertions(replace_point, 'right')
+    aligned_replace_region = sublime.Region(aligned_replace_point, aligned_replace_point + erase)
+    self.view.replace(edit, aligned_replace_region, replace)
+    self.__inserted_offsets.append((replace_point, len(replace) - erase))
+    return (region, direction, 0, backoff)
+
+  def perform_wrap(self, edit, region, insert_left, insert_right, move_side = None):
+    if region.a < region.b:
+      left = region.a
+      right = region.b
+    else:
+      left = region.b
+      right = region.a
+
+    aligned_insert_point = self.__align_point_after_insertions(left, 'left')
+    insert_offset = self.view.insert(edit, aligned_insert_point, insert_left)
+    self.__inserted_offsets.append((left, insert_offset))
+
+    aligned_insert_point = self.__align_point_after_insertions(right, 'right')
+    insert_offset = self.view.insert(edit, aligned_insert_point, insert_right)
+    self.__inserted_offsets.append((right, insert_offset))
+
+    if move_side == 'left':
+      return (region, 'collapse-left', 0, 0)
+    else:
+      return (region, 'collapse-right', 0, 0)
+
+def _match_tag_in_progress(view, region):
+  to_start = sublime.Region(view.line(region).a, region.b)
+  match = re.search('(\[\[(?:(?!]]|\|).)*)$', view.substr(to_start))
+  if match:
+    return match.group(1)
+
+def _match_rest_of_tag_in_progress(view, region):
+  to_end = sublime.Region(region.a, view.line(region).b)
+  match = re.search('^((?:(?!\[\[).)*]])', view.substr(to_end))
+  if match:
+    return match.group(1)
+
+def _consider_reference_cycle(view, region, cycle):
+  tag_in_progress = _match_tag_in_progress(view, region)
+
+  if not tag_in_progress:
+    return None
+
+  rest_of_tag = _match_rest_of_tag_in_progress(view, region)
+
+  rest_of_ref = re.search('^((?:(?!\|).)*:)', rest_of_tag)
+  if rest_of_ref:
+    return ('move', len(rest_of_ref.group(1)))
+
+  if tag_in_progress == '[[':
+    current_ref = None
+    insert_pipe = rest_of_tag != ']]' and '|' not in rest_of_tag
+  elif tag_in_progress[-1:] == ':':
+    current_ref = tag_in_progress[2:-1]
+    insert_pipe = False
+  else:
+    return None
+
+  if current_ref in cycle:
+    next_ref = cycle[(cycle.index(current_ref) + 1) % len(cycle)]
+  else:
+    next_ref = cycle[0]
+
+  if current_ref:
+    return ('replace', 'right', len(current_ref) + 1, next_ref + ':', 0)
+  elif insert_pipe:
+    return ('insert', 'right', next_ref + ':|', 1)
+  else:
+    return ('insert', 'right', next_ref + ':', 0)
+
+class EnterExitWikiTagCommand(_CursorAdaptiveCommand):
+  ref_cycle = ['artist', 'group', 'flash', 'tag']
+
+  def handle_region(self, region):
+    cycle_action = _consider_reference_cycle(self.view, region, self.ref_cycle)
+    if cycle_action:
+      return cycle_action
+
+    rest_of_tag = _match_rest_of_tag_in_progress(self.view, region)
+    if rest_of_tag:
+      return ('move', len(rest_of_tag))
+    elif region.a == region.b:
+      return ('insert', 'right', '[[]]', 2)
+    else:
+      return ('wrap', '[[', ']]', 'left')
+
+class HyphenInWikiTagCommand(_CursorAdaptiveCommand):
+  ref_cycle = ['artist', 'group', 'flash', 'tag']
+
+  def handle_region(self, region):
+    cycle_action = _consider_reference_cycle(self.view, region, self.ref_cycle)
+    if cycle_action:
+      return cycle_action
+
+    tag_in_progress = _match_tag_in_progress(self.view, region)
+
+    if tag_in_progress and ':' in tag_in_progress:
+      return ('insert', 'right', '-', 0)
+
+class SpaceInWikiTagCommand(_CursorAdaptiveCommand):
+  ref_cycle = ['track', 'album']
+
+  handle_region_fallback = ('insert', 'right', ' ', 0)
+
+  def handle_region(self, region):
+    cycle_action = _consider_reference_cycle(self.view, region, self.ref_cycle)
+    if cycle_action:
+      return cycle_action
+
+    tag_in_progress = _match_tag_in_progress(self.view, region)
+
+    if tag_in_progress and ':' in tag_in_progress:
+      if self.view.substr(region.b) == '|':
+        return ('move', 1)
+      else:
+        return ('insert', 'right', '|', 0)