我的印象是 Sublime 也提供了这种能力。当我发现它不是时,我有了使用正则表达式的想法。尽管 regex 通常被认为不适合解析 XML/HTML,但我发现这种方法在这种情况下是可以接受的。据说 Sublime 也可以通过插件高度定制,所以我认为这将是一种方式。
Sublime 插件
说实话,我本可以想到 tidy 或者至少怀疑肯定有插件可以解决您的问题。相反,我最终编写了我的第一个 sublime 插件。我只用你的输入和预期的输出对它进行了测试,它满足了,但它肯定离可靠地工作还很远。但是,我在这里发布它以分享我所学到的东西,它仍然是问题的答案。
打开一个新缓冲区 (Ctrl+n) 并在菜单“工具”中选择“New Plugin...”条目慷慨地生成一个小“Hello World!”示例插件(作为 Python 模块),它为实现 sublime_plugin.TextCommand 子类提供了一个很好的模板。 TextCommand 提供对活动缓冲区/当前打开文件的访问。像它的亲戚WindowCommand 和ApplicationCommand 一样,它需要覆盖一个运行方法。
official API Reference 建议通过阅读随 Sublime 构建分发并位于相对于 Sublime 配置路径的Packages/Default 的示例源来学习。更多示例可以在website 上找到。有moreon 互联网。
处理选定的文本
要为您的问题找到解决方案,我们主要需要访问代表活动文本缓冲区的View 对象。好在我们即将实现的TextCommand子类有一个,我们可以方便地向它查询当前选中的区域及其选中内容,处理符合我们需要的选中文本,然后用我们的偏好替换选中的文本。
总结一下字符串操作:有四个正则表达式,每个都匹配元素类<start-tag>、<empty-tag/>、</close-tag>和text-node之一。假设我们所有的标记文本都被这些覆盖,我们将选择中的每一行都放入匹配的子字符串中。然后每行重新对齐这些。完成此操作后,我们通过记住缩进其前身包含开始标记的每一行来应用简单的缩进。包含结束标记的行立即取消缩进。
使用 Python 正则表达式的group addressing 特性,我们可以确定每一行的缩进并相应地对齐下一行。事不宜迟,这将导致内部一致的缩进标记,但不考虑选择之外的行。通过将选择扩展到封闭元素,或者至少符合相邻行的缩进级别,可以轻松改善结果。总是可以使用default commands。
另一件需要注意的事情是将键绑定到插件命令和贡献菜单条目。可能somehow 是可能的,Packages/Default 中的默认.sublime-menu 和.sublime-commands 文件至少给出了一个想法。无论如何,这里有一些代码。它必须保存在Packages/User/whatever.py 下,并且可以从 Sublime Python 控制台 (Ctrl+`) 调用,如下所示:view.run_command('guess_indentation')。
代码
import sublime
import sublime_plugin
import re
class GuessIndentationCommand(sublime_plugin.TextCommand):
def run(self, edit):
view = self.view
#view.begin_edit()
# patterns
start_tag = '<\w+(?:\s+[^>\/]+)*\s*>' # tag_start
node_patterns = [start_tag,
start_tag[:-1]+'\/\s*>', # tag_empty
'<\/\s?\w+\s?>', # tag_close
'[^>\s][^<>]*[^<\s]'] # text_node
patterns = '(?:{0})'.format('|'.join(node_patterns))
indentors = re.compile('[ \t]*({0})'.format('|'.join(node_patterns[:1])))
unindentors=re.compile('[ \t]*({0})'.format(node_patterns[2]))
# process selected text
for region in view.sel():
# if selection contains text:
if not region.empty():
selection = view.substr(region)
expanded = []
# divide selected lines into XML elements, if it contains more than one
for line in selection.split('\n'):
elements = re.findall(patterns, line)
if len(elements)>0:
expanded += elements
else:
expanded.append(line)
# indent output
indent=0
indented = []
for line in expanded:
match = unindentors.match(line)
if match:
indent = max(0, indent-1)
# append line to output, unindented if closing tag
indented.append('\t'*indent+line)
if match:
continue
# test for possible indentation candidate
# indentation applies to the NEXT line
match = indentors.match(line)
if match:
indent+=1
# replace selection with aligned output
view.replace(edit, region, '\n'.join(indented))