From e025da5cf246a017d2de9a8a222abe1eedf88ba6 Mon Sep 17 00:00:00 2001 From: "rygwdn@gmail.com" <> Date: Mon, 16 Aug 2010 21:59:41 -0300 Subject: [PATCH 1/3] Changed local variables in python code blocks to persist across blocks in a snippet. Removed "snips.locals". Also added ability to use file-wide "global" snippets. Added tests for both. --- plugin/UltiSnips/TextObjects.py | 36 +++++++++++------------- plugin/UltiSnips/__init__.py | 49 +++++++++++++++++++++++---------- test.py | 41 ++++++++++++++++++++++++--- 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/plugin/UltiSnips/TextObjects.py b/plugin/UltiSnips/TextObjects.py index 1651677..c88f274 100644 --- a/plugin/UltiSnips/TextObjects.py +++ b/plugin/UltiSnips/TextObjects.py @@ -705,17 +705,10 @@ class _Tabs(object): return ts.current_text class SnippetUtil(object): - """ Provides easy access to indentation, and - snippet-local variables, which can be accessed by - any PythonCode object in the snippet. + """ Provides easy access to indentation, etc. """ - def __init__(self, initial_indent, cur="", snippet=None): - if snippet: - self._locals = snippet.locals - else: - self._locals = {} - + def __init__(self, initial_indent, cur=""): self._sw = int(vim.eval("&sw")) self._sts = int(vim.eval("&sts")) self._et = (vim.eval("&expandtab") == "1") @@ -842,11 +835,6 @@ class SnippetUtil(object): """ return self._c - @property - def locals(self): - """ Provides snippet local variables. """ - return self._locals - def opt(self, option, default=None): """ Gets a vim variable. """ if vim.eval("exists('%s')" % option) == "1": @@ -884,7 +872,12 @@ class PythonCode(TextObject): snippet = snippet._parent except AttributeError: snippet = None - self._snip = SnippetUtil(indent, snippet=snippet) + self._snip = SnippetUtil(indent) + self._locals = snippet.locals + + self._globals = {} + globals = snippet.globals.get("!p", []) + exec "\n".join(globals) in self._globals # Add Some convenience to the code self._code = "import re, os, vim, string, random\n" + code @@ -900,21 +893,23 @@ class PythonCode(TextObject): ct = self.current_text self._snip._reset(ct) - d = { + local_d = self._locals + + local_d.update({ 't': _Tabs(self), 'fn': fn, 'path': path, 'cur': ct, 'res': ct, 'snip' : self._snip, - } + }) - exec self._code in d + exec self._code in self._globals, local_d if self._snip._rv_changed: self.current_text = self._snip.rv else: - self.current_text = str(d["res"]) + self.current_text = str(local_d["res"]) def __repr__(self): return "PythonCode(%s -> %s)" % (self._start, self._end) @@ -945,13 +940,14 @@ class SnippetInstance(TextObject): """ # TODO: for beauty sake, start and end should come before initial text - def __init__(self, parent, indent, initial_text, start = None, end = None, last_re = None): + def __init__(self, parent, indent, initial_text, start = None, end = None, last_re = None, globals = None): if start is None: start = Position(0,0) if end is None: end = Position(0,0) self.locals = {"match" : last_re} + self.globals = globals TextObject.__init__(self, parent, start, end, initial_text) diff --git a/plugin/UltiSnips/__init__.py b/plugin/UltiSnips/__init__.py index 1863387..b8c89ee 100644 --- a/plugin/UltiSnips/__init__.py +++ b/plugin/UltiSnips/__init__.py @@ -63,6 +63,7 @@ class _SnippetsFileParser(object): self._sm = snip_manager self._ft = ft self._fn = fn + self._globals = {} if file_data is None: self._lines = open(fn).readlines() else: @@ -96,17 +97,17 @@ class _SnippetsFileParser(object): self._idx += 1 return self._line() - def _parse_snippet(self): - line = self._line() - + def _parse_first(self, line): + """ Parses the first line of the snippet definition. Returns the + snippet type, trigger, description, and options in a tuple in that + order. + """ cdescr = "" coptions = "" cs = "" # Ensure this is a snippet snip = line.split()[0] - if snip != "snippet": - self._error("Expecting 'snippet' not: %s" % snip) # Get and strip options if they exist remain = line[len(snip):].lstrip() @@ -133,17 +134,36 @@ class _SnippetsFileParser(object): else: cs = cs[1:-1] + return (snip, cs, cdescr, coptions) + + def _parse_snippet(self): + line = self._line() + + (snip, trig, desc, opts) = self._parse_first(line) + + if not trig: + self._error("Missing trigger for snippet") + return None + cv = "" while self._goto_next_line(): line = self._line() if line.rstrip() == "endsnippet": cv = cv[:-1] # Chop the last newline - if cs: - self._sm.add_snippet(cs, cv, cdescr, coptions, self._ft) break cv += line else: - self._error("Missing 'endsnippet' for %r" % cs) + self._error("Missing 'endsnippet' for %r" % trig) + + + if snip == "global": + if trig not in self._globals: + self._globals[trig] = [] + self._globals[trig].append(cv) + elif snip == "snippet": + self._sm.add_snippet(trig, cv, desc, opts, self._ft, self._globals) + else: + self._error("Invalid snippet type: '%s'" % snip) def parse(self): while self._line(): @@ -154,7 +174,7 @@ class _SnippetsFileParser(object): [ p.strip() for p in tail.split(',') ]) else: self._error("'extends' without file types") - elif head == "snippet": + elif head in ("snippet", "global"): self._parse_snippet() elif head == "clearsnippets": self._sm.clear_snippets(tail.split(), self._ft) @@ -168,13 +188,14 @@ class _SnippetsFileParser(object): class Snippet(object): _INDENT = re.compile(r"^[ \t]*") - def __init__(self, trigger, value, descr, options): + def __init__(self, trigger, value, descr, options, globals): self._t = trigger self._v = value self._d = descr self._opts = options self._matched = "" self._last_re = None + self._globals = globals def __repr__(self): return "Snippet(%s,%s,%s)" % (self._t,self._d,self._opts) @@ -337,10 +358,10 @@ class Snippet(object): if parent is None: return SnippetInstance(StartMarker(start), indent, - v, last_re = self._last_re) + v, last_re = self._last_re, globals = self._globals) else: return SnippetInstance(parent, indent, v, start, - end, last_re = self._last_re) + end, last_re = self._last_re, globals = self._globals) class VimState(object): def __init__(self): @@ -504,11 +525,11 @@ class SnippetManager(object): if not rv: self._handle_failure(self.expand_trigger) - def add_snippet(self, trigger, value, descr, options, ft = "all"): + def add_snippet(self, trigger, value, descr, options, ft = "all", globals = None): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() l = self._snippets[ft].add_snippet( - Snippet(trigger, value, descr, options) + Snippet(trigger, value, descr, options, globals or []) ) def clear_snippets(self, triggers = [], ft = "all"): diff --git a/test.py b/test.py index 7c29faf..0ab08b3 100755 --- a/test.py +++ b/test.py @@ -739,8 +739,8 @@ class PythonCode_OptNoExists(_VimTest): # locals class PythonCode_Locals(_VimTest): - snippets = ("test", r"""hi `!p snip.locals["a"] = "test" -snip.rv = "nothing"` `!p snip.rv = snip.locals["a"] + snippets = ("test", r"""hi `!p a = "test" +snip.rv = "nothing"` `!p snip.rv = a ` End""") keys = """test""" + EX wanted = """hi nothing test End""" @@ -1468,14 +1468,14 @@ class SnippetOptions_Regex_Self_TextBefore(_Regex_Self): wanted = "a." + EX class SnippetOptions_Regex_PythonBlockMatch(_VimTest): - snippets = (r"([abc]+)([def]+)", r"""`!p m = snip.locals["match"] + snippets = (r"([abc]+)([def]+)", r"""`!p m = match snip.rv += m.group(2) snip.rv += m.group(1) `""", "", "r") keys = "test cabfed" + EX wanted = "test fedcab" class SnippetOptions_Regex_PythonBlockNoMatch(_VimTest): - snippets = (r"cabfed", r"""`!p snip.rv = snip.locals["match"] or "No match"`""") + snippets = (r"cabfed", r"""`!p snip.rv = match or "No match"`""") keys = "test cabfed" + EX wanted = "test No match" @@ -1805,6 +1805,39 @@ class ParseSnippets_MultiWord_UnmatchedContainer(_VimTest): UltiSnips: Invalid multiword trigger: '!inv snip/' in test_file(2) """).strip() +class ParseSnippets_Global_Python(_VimTest): + snippets_test_file = ("all", "test_file", r""" + global !p + def tex(ins): + return "a " + ins + " b" + endsnippet + + snippet ab + x `!p snip.rv = tex("bob")` y + endsnippet + + snippet ac + x `!p snip.rv = tex("jon")` y + endsnippet + """) + keys = "ab" + EX + "\nac" + EX + wanted = "x a bob b y\nx a jon b y" + +class ParseSnippets_Global_Local_Python(_VimTest): + snippets_test_file = ("all", "test_file", r""" +global !p +def tex(ins): + return "a " + ins + " b" +endsnippet + +snippet ab +x `!p first = tex("bob") +snip.rv = "first"` `!p snip.rv = first` y +endsnippet + """) + keys = "ab" + EX + wanted = "x first a bob b y" + ########################################################################### # END OF TEST # From 4b852669291eb5212fe63b416e40f3e1542cd8bf Mon Sep 17 00:00:00 2001 From: "rygwdn@gmail.com" <> Date: Tue, 17 Aug 2010 00:12:30 -0300 Subject: [PATCH 2/3] Added documentation for globals, and fixed docs for locals. Fixed error messages to match tests. Changed endsnippet -> endglobal for global snippets. --- doc/UltiSnips.txt | 33 +++++++++++++++++++++++++++------ plugin/UltiSnips/__init__.py | 19 ++++++++++--------- test.py | 4 ++-- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/doc/UltiSnips.txt b/doc/UltiSnips.txt index 442fd06..c03d574 100644 --- a/doc/UltiSnips.txt +++ b/doc/UltiSnips.txt @@ -15,6 +15,7 @@ UltiSnips *snippet* *snippets* *UltiSnips* 4.3.1 Shellcode |UltiSnips-shellcode| 4.3.2 VimScript |UltiSnips-vimscript| 4.3.3 Python |UltiSnips-python| + 4.3.4 Global Snippets |UltiSnips-globals| 4.4 Tab Stops and Placeholders |UltiSnips-tabstops| 4.6 Mirrors |UltiSnips-mirrors| 4.7 Transformations |UltiSnips-transformations| @@ -202,7 +203,7 @@ snippet on. The options currently supported are > match. The regular expression MUST be surrounded like a multi-word trigger (see above) even if it doesn't have any spaces. The resulting match is also passed to any python code blocks in your snippet - definition in the as "snips.local['match']". + definition as the local variable "match". 4.2 Plaintext snippets *UltiSnips-plaintext-snippets* ---------------------- @@ -281,7 +282,7 @@ which can be used: > fn - The current filename path - The complete path to the current file t - The values of the placeholders, t[1] -> current text of ${1} and so on - snip - Provides easy indentation handling, and snippet-local variables. + snip - Provides easy indentation handling. The snip object provides the following methods: > @@ -307,10 +308,6 @@ The snip object provides the following methods: > The snip object provides some properties as well: > - snip.locals: - Is a dictionary which is available to any python block inside the - snippet. - snip.rv: the text that will fill this python block's position, it always starts out as an empty string. This deprecates the "res" variable. @@ -339,6 +336,7 @@ easier: > snip += line: is equivalent to "snip.rv += '\n' + snip.mkline(line)" +Any variables set in a python block can be used in any following blocks. Also, the vim, re, os, string and random modules are already imported inside the snippet code. This allows for very flexible snippets. For example, the following snippet mirrors the first Tab Stops value on the same line in @@ -352,6 +350,29 @@ endsnippet wowHello World -> Hello World HELLO WORLD + 4.3.4 Global Snippets: *UltiSnips-globals* + +Global snippets provide a way to take common code out of snippets. Currently, +only python code is supported. The result of executing the contents of the +snippet is put into the globals of each python block in the snippet file. To +create a global snippet, you use the keyword "global" in place of "snippet", +and for python code, you use "!p" for the trigger, for example, the following +is identical to the previous example, except that "upper_right" can be reused: + +------------------- SNIP ------------------- +global !p +def upper_right(inp): + return (75 - 2 * len(inp))*' ' + inp.upper() +endglobal + +snippet wow +${1:Text}`!p snip.rv = upper_right(t[1])` +endsnippet +------------------- SNAP ------------------- +wowHello World -> +Hello World HELLO WORLD + + 4.4 Tab Stops and Placeholders *UltiSnips-tabstops* *UltiSnips-placeholders* ------------------------------ diff --git a/plugin/UltiSnips/__init__.py b/plugin/UltiSnips/__init__.py index b8c89ee..12d42a4 100644 --- a/plugin/UltiSnips/__init__.py +++ b/plugin/UltiSnips/__init__.py @@ -140,23 +140,24 @@ class _SnippetsFileParser(object): line = self._line() (snip, trig, desc, opts) = self._parse_first(line) - - if not trig: - self._error("Missing trigger for snippet") - return None - + end = "end" + snip cv = "" + while self._goto_next_line(): line = self._line() - if line.rstrip() == "endsnippet": + if line.rstrip() == end: cv = cv[:-1] # Chop the last newline break cv += line else: self._error("Missing 'endsnippet' for %r" % trig) + return None - - if snip == "global": + if not trig: + # there was an error + return None + elif snip == "global": + # add snippet contents to file globals if trig not in self._globals: self._globals[trig] = [] self._globals[trig].append(cv) @@ -529,7 +530,7 @@ class SnippetManager(object): if ft not in self._snippets: self._snippets[ft] = _SnippetDictionary() l = self._snippets[ft].add_snippet( - Snippet(trigger, value, descr, options, globals or []) + Snippet(trigger, value, descr, options, globals or {}) ) def clear_snippets(self, triggers = [], ft = "all"): diff --git a/test.py b/test.py index 0ab08b3..b647503 100755 --- a/test.py +++ b/test.py @@ -1810,7 +1810,7 @@ class ParseSnippets_Global_Python(_VimTest): global !p def tex(ins): return "a " + ins + " b" - endsnippet + endglobal snippet ab x `!p snip.rv = tex("bob")` y @@ -1828,7 +1828,7 @@ class ParseSnippets_Global_Local_Python(_VimTest): global !p def tex(ins): return "a " + ins + " b" -endsnippet +endglobal snippet ab x `!p first = tex("bob") From 514d9d1bd74dd0f0dff641e7762496c8cd03cffd Mon Sep 17 00:00:00 2001 From: "rygwdn@gmail.com" <> Date: Tue, 17 Aug 2010 00:58:44 -0300 Subject: [PATCH 3/3] updated some snippets to use new global and locals features. --- UltiSnips/all.snippets | 33 +++++++++++++-------------- UltiSnips/help.snippets | 27 +++++++++------------- UltiSnips/html.snippets | 30 ++++++++++++++++--------- UltiSnips/python.snippets | 47 ++++++++++++++++++--------------------- 4 files changed, 67 insertions(+), 70 deletions(-) diff --git a/UltiSnips/all.snippets b/UltiSnips/all.snippets index 1df4297..0a0aa3d 100644 --- a/UltiSnips/all.snippets +++ b/UltiSnips/all.snippets @@ -4,41 +4,40 @@ ############## # NICE BOXES # ############## +global !p +def cs(snip): + c = '#' + cs = snip.opt("&commentstring") + if len(cs) == 3: + c = cs[0] + return c +endglobal + snippet box "A nice box with the current comment symbol" b `!p -c = '#' -cs = vim.eval("&commentstring") -if len(cs) == 3: - c = cs[0] -snip.locals["c"] = c +c = cs(snip) snip.rv = (len(t[1])+4)*c -snip.locals["bar"] = snip.rv +bar = snip.rv snip += c + ' '`${1:content}`!p -c = snip.locals["c"] snip.rv = ' ' + c -snip += snip.locals["bar"]` +snip += bar` $0 endsnippet snippet bbox "A nice box over the full width" b `!p -c = '#' -cs = vim.eval("&commentstring") -if len(cs) == 3: - c = cs[0] -snip.locals["c"] = c -snip.locals["bar"] = 75*c +c = cs(snip) +bar = 75*c -snip.rv = snip.locals["bar"] +snip.rv = bar snip += c + " " + (71-len(t[1]))/2*' ' `${1:content}`!p -c = snip.locals["c"] a = 71-len(t[1]) snip.rv = (a/2 + a%2) * " " + " " + c -snip += snip.locals["bar"]` +snip += bar` $0 endsnippet diff --git a/UltiSnips/help.snippets b/UltiSnips/help.snippets index 0a002e1..ad28431 100644 --- a/UltiSnips/help.snippets +++ b/UltiSnips/help.snippets @@ -1,35 +1,28 @@ # Snippets for VIM Help Files +global !p +def sec_title(snip, t): + file_start = snip.fn.split('.')[0] + sec_name = t[1].strip("1234567890. ").lower().replace(' ', '-') + return ("*%s-%s*" % (file_start, sec_name)).rjust(77-len(t[1])) +endglobal + snippet sec "Section marker" b ============================================================================= -${1:SECTION}`!p -file_start = snip.fn.split('.')[0] -sec_name = t[1].strip("1234567890. ").lower().replace(' ', '-') - -snip.rv = ("*%s-%s*" % (file_start, sec_name)).rjust(77-len(t[1]))` +${1:SECTION}`!p snip.rv = sec_title(snip, t)` $0 endsnippet snippet ssec "Sub section marker" b -${1:Subsection}`!p -file_start = snip.fn.split('.')[0] -sec_name = t[1].strip("1234567890. ").lower().replace(' ', '-') -sec_title = ("*%s-%s*" % (file_start, sec_name)).rjust(77-len(t[1])) - -snip.rv = sec_title +${1:Subsection}`!p snip.rv = sec_title(snip, t) snip += "-"*len(t[1])` $0 endsnippet snippet sssec "Subsub Section marker" b -${1:SubSubsection}:`!p -file_start = fn.split('.')[0] -sec_name = t[1].strip("1234567890. ").lower().replace(' ', '-') -sec_title = ("*%s-%s*" % (file_start, sec_name)).rjust(77-len(t[1])) - -snip.rv = sec_title` +${1:SubSubsection}:`!p snip.rv = sec_title(snip, t)` $0 endsnippet diff --git a/UltiSnips/html.snippets b/UltiSnips/html.snippets index bb1a585..e977be5 100644 --- a/UltiSnips/html.snippets +++ b/UltiSnips/html.snippets @@ -2,6 +2,14 @@ # TextMate Snippets # ########################################################################### +global !p +def x(snip): + if snip.ft.startswith("x"): + snip.rv = '/' + else: + snip.rv = "" +endglobal + ############ # Doctypes # ############ @@ -112,12 +120,12 @@ endsnippet # HTML TAGS # ############# snippet input "Input with Label" - + endsnippet snippet input "XHTML " - + endsnippet @@ -143,7 +151,7 @@ snippet mailto "XHTML " endsnippet snippet base "XHTML " - + endsnippet snippet body "XHTML " @@ -166,7 +174,7 @@ snip.rv = (snip.basename or 'unnamed') + '_submit' `}" method="${2:get}" accept-charset="utf-8"> $0 -

+

endsnippet @@ -176,18 +184,18 @@ endsnippet snippet head "XHTML " - + ${1:`!p snip.rv = snip.basename or "Page Title"`} $0 endsnippet snippet link "XHTML " - + endsnippet snippet meta "XHTML " - + endsnippet snippet scriptsrc "XHTML