Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Code completion?! YES! But what about context sensitive API doc toolt…

…ips?! Yup, got those too.

Yesiree, Bob.
  • Loading branch information...
commit c313aa3df9396e51f709fdc4bf91073ab2335caf 1 parent 57b6cc5
@subtleGradient authored
View
2  Commands/Appcelerator Titanium Mobile Developer Center.tmCommand
@@ -18,7 +18,7 @@
<key>output</key>
<string>showAsTooltip</string>
<key>scope</key>
- <string>source.js</string>
+ <string>source.js.ti.mobile</string>
<key>uuid</key>
<string>E910E64B-7423-4B47-8F18-1847D4DEE85B</string>
</dict>
View
25 Commands/Code Completion.tmCommand
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>beforeRunningCommand</key>
+ <string>nop</string>
+ <key>command</key>
+ <string>#!/usr/bin/env ruby
+require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete'
+TextMate::Complete.new.complete!
+</string>
+ <key>input</key>
+ <string>none</string>
+ <key>keyEquivalent</key>
+ <string>~</string>
+ <key>name</key>
+ <string>Code Completion</string>
+ <key>output</key>
+ <string>showAsTooltip</string>
+ <key>scope</key>
+ <string>source.js.ti</string>
+ <key>uuid</key>
+ <string>88506B58-5A8F-4009-8F5A-AC39182378EB</string>
+</dict>
+</plist>
View
27 Commands/Documentation for Word : Selection (tool tip).tmCommand
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>beforeRunningCommand</key>
+ <string>nop</string>
+ <key>command</key>
+ <string>#!/usr/bin/env ruby
+require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete'
+TextMate::Complete.new.tip!
+</string>
+ <key>fallbackInput</key>
+ <string>word</string>
+ <key>input</key>
+ <string>none</string>
+ <key>keyEquivalent</key>
+ <string>~</string>
+ <key>name</key>
+ <string>Documentation for Word / Selection (tool tip)</string>
+ <key>output</key>
+ <string>showAsTooltip</string>
+ <key>scope</key>
+ <string>source.js.ti</string>
+ <key>uuid</key>
+ <string>59FF1465-9977-49BE-97F8-3205CBF011C5</string>
+</dict>
+</plist>
View
49 Macros/Ti_ Completion.tmMacro
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>commands</key>
+ <array>
+ <dict>
+ <key>argument</key>
+ <string>.</string>
+ <key>command</key>
+ <string>insertText:</string>
+ </dict>
+ <dict>
+ <key>argument</key>
+ <dict>
+ <key>beforeRunningCommand</key>
+ <string>nop</string>
+ <key>command</key>
+ <string>#!/usr/bin/env ruby
+require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete'
+TextMate::Complete.new.complete!
+</string>
+ <key>input</key>
+ <string>none</string>
+ <key>keyEquivalent</key>
+ <string>~</string>
+ <key>name</key>
+ <string>Code Completion</string>
+ <key>output</key>
+ <string>showAsTooltip</string>
+ <key>scope</key>
+ <string>source.js.ti</string>
+ <key>uuid</key>
+ <string>88506B58-5A8F-4009-8F5A-AC39182378EB</string>
+ </dict>
+ <key>command</key>
+ <string>executeCommandWithOptions:</string>
+ </dict>
+ </array>
+ <key>keyEquivalent</key>
+ <string>.</string>
+ <key>name</key>
+ <string>Ti. Completion</string>
+ <key>scope</key>
+ <string>meta.complete.method.ti</string>
+ <key>uuid</key>
+ <string>1B66F6F3-D2F3-4324-A11E-E0E5F8670AD7</string>
+</dict>
+</plist>
View
26 Preferences/Completions.tmPreferences
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>name</key>
+ <string>Completions</string>
+ <key>scope</key>
+ <string>source.js</string>
+ <key>settings</key>
+ <dict>
+ <key>completions</key>
+ <array/>
+ <key>shellVariables</key>
+ <array>
+ <dict>
+ <key>name</key>
+ <string>TM_COMPLETIONS_FILES</string>
+ <key>value</key>
+ <string>"completions.json"</string>
+ </dict>
+ </array>
+ </dict>
+ <key>uuid</key>
+ <string>E4384F6B-629C-4F74-A011-59D3BF40D2B5</string>
+</dict>
+</plist>
View
16 Snippets/Ti_API_debug.tmSnippet
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>content</key>
+ <string>Ti.API.debug(${0:${TM_SELECTED_TEXT}})</string>
+ <key>name</key>
+ <string>Ti.API.debug</string>
+ <key>scope</key>
+ <string>source.js.ti</string>
+ <key>tabTrigger</key>
+ <string>d</string>
+ <key>uuid</key>
+ <string>1BE04E27-BDCF-4B7D-B7A5-479D0478689F</string>
+</dict>
+</plist>
View
16 Snippets/include().tmSnippet
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>content</key>
+ <string>Ti.include($0);</string>
+ <key>name</key>
+ <string>Ti.include()</string>
+ <key>scope</key>
+ <string>source.js.ti</string>
+ <key>tabTrigger</key>
+ <string>inc</string>
+ <key>uuid</key>
+ <string>602D47B3-1101-483B-A11E-86550C77E229</string>
+</dict>
+</plist>
View
24,602 Support/api.json
24,602 additions, 0 deletions not shown
View
1  Support/completions.json
1 addition, 0 deletions not shown
View
382 Support/generateCompletions.js
@@ -0,0 +1,382 @@
+#!/usr/bin/env node
+
+var sys = require('sys');
+var fs = require('fs');
+
+function Suggestion(options){
+ if (!(this instanceof Suggestion)) return new Suggestion(options);
+
+ if (options) for (var property in options){
+ if (!hasOwnProperty.call(options, property)) continue;
+ if (typeof options[property] === 'undefined') continue;
+ this[property] = options[property];
+ }
+
+ this.insert = (new Snippet()).appendRaw(this.insert);
+}
+Suggestion.prototype = {
+ tool_tip_format : "html",
+ tool_tip : "",
+ insert : "$0", // new Snippet()
+ match : "suggestion",
+ display : "My suggestion of Doom!",
+ image : "",
+
+ toJSON: function(){
+ return {
+ tool_tip_format : "" + this.tool_tip_format ,
+ tool_tip : "" + this.tool_tip ,
+ insert : "" + this.insert ,
+ match : "" + this.match ,
+ display : "" + this.display ,
+ image : "" + this.image
+ };
+ }
+};
+
+
+Suggestion.Method = function(options){
+ if (!(this instanceof Suggestion.Method)) return new Suggestion.Method(options);
+ Suggestion.call(this, options);
+ this.params = [];
+};
+Suggestion.Method.prototype = Object.create(Suggestion.prototype);
+Suggestion.Method.prototype.image = "Method";
+Suggestion.Method.prototype.insert = "($0)";
+Suggestion.Method.prototype.updateToolTip = function(){
+ tip = '';
+
+ tip += this.description.replace(/^\s*(<p>)?/,'<p>');
+
+ tip += '<p><code class="usage">';
+ tip += '<b><u>';
+ tip += this.display.replace(/\t/g,'').replace(/\:|$/,'</u></b><i>:');
+ tip += "</i><br>(\n";
+
+ var objectKey;
+ var objectProperties;
+
+ if (this.params && this.params.length)
+ for (var i = -1, param; param = this.params[++i];){
+
+ if (param.description) if (objectKey = param.description.match(/properties defined in .*?>([^<]*)</)){
+ objectKey = objectKey[1];
+ objectProperties = API[objectKey].properties;
+ // sys.p(objectKey, objectProperties);
+
+ tip += "{\n"
+ for (var p = -1; p < objectProperties.length; ++p){
+ if (!objectProperties[p]) continue;
+ tip += describeProperty(objectProperties[p]);
+ }
+ tip += "}\n"
+ continue;
+ }
+
+ tip += "\t";
+ tip += describeProperty(param);
+ }
+ tip += ")\n"
+ tip += '</code>'
+
+ sys.print(tip.replace(/</g,'&lt;'));
+
+ this.tool_tip = tip;
+};
+
+function describeProperty(p){
+ var html = "\t"
+ + "<span>"
+ + "<b>"
+ + p.name
+ + "</b>"
+ + "<i>"
+ + ":"
+ + p.type
+ + "</i>"
+ + "</span>"
+ + "\n\t\t"
+ + "<span class='info'>"
+ + (p.description || p.value).replace(/<[^>]*>/,'')
+ + "</span>"
+ + "\n"
+ ;
+ return html;
+}
+
+
+Suggestion.Method.prototype.updateInsertSnippet = function(){
+ snip = new Snippet('');
+ var add;
+ if (this.params && this.params.length){
+ for (var i = -1, param; param = this.params[++i];){
+ if (i == 0) snip.appendText("(").begin();
+ else snip.appendText(", ");
+
+ var isLast = !this.params[i+1];
+ add = (isLast ? 'addLast' : 'addPlaceholder');
+
+ switch (param.type){
+ case 'string': snip.appendText("'")[add](param.name).appendText("'"); break;
+ case 'object': snip.appendText("{")[add]().appendText("}"); break;
+ case 'function': snip.appendText("function ").addPlaceholder(param.name).appendText("(").addPlaceholder().appendText(")").appendText("{")[add]().appendText("}"); break;
+ default:
+ snip.addPlaceholder(param.name);
+ }
+ }
+ snip.end().appendText(')');
+ }
+ else snip.appendRaw('($0)');
+
+ // sys.p(snip)
+
+ this.insert = snip;
+};
+Suggestion.Method.prototype.addParams = function(params){
+ if (!(params && params.length)) return this;
+ for (var i = -1, param; param = params[++i];){
+ this.addParam(param, false);
+ }
+ this.updateToolTip();
+ this.updateInsertSnippet();
+ return this;
+};
+Suggestion.Method.prototype.addParam = function(param, shouldUpdate){
+ this.params.push(param);
+ if (shouldUpdate !== false) this.updateToolTip().updateInsertSnippet();
+ return this;
+};
+
+
+Suggestion.Property = function(string){
+ if (!(this instanceof Suggestion.Property)) return new Suggestion.Property(string);
+ Suggestion.call(this, options);
+};
+Suggestion.Property.prototype = Object.create(Suggestion.prototype);
+Suggestion.Property.prototype.image = "Property";
+
+
+function Snippet(string){
+ if (!(this instanceof Snippet)) return new Snippet(string);
+
+ this.snippet = '';
+ this.appendText(string || '');
+ this.placeholderID = 0;
+}
+Snippet.prototype = {
+
+ appendRaw: function(string){
+ this.snippet += string;
+ return this;
+ },
+
+ appendText: function(string){
+ this.snippet += Snippet[this.insidePlaceholder ? 'escapePlaceholderValue' : 'escape'](string);
+ return this;
+ },
+
+ addPlaceholder: function(value, escape){
+ this.appendPlaceholder(++ this.placeholderID, value, escape);
+ return this;
+ },
+
+ addLast: function(value, escape){
+ this.appendPlaceholder(0, value, escape);
+ return this;
+ },
+
+ appendPlaceholder: function(key, value, escape){
+ if (typeof escape === 'undefined') escape = true;
+ if (!value) value = '';
+ if (escape) value = Snippet.escapePlaceholderValue(value);
+ this.snippet += "${"+ key +":"+ value +"}";
+ return this;
+ },
+
+ begin: function(key){
+ if (key == null) key = ++ this.placeholderID;
+ this.snippet += "${"+ key +":";
+ this.insidePlaceholder = true;
+ return this;
+ },
+
+ end: function(){
+ this.snippet += "}";
+ this.insidePlaceholder = false;
+ return this;
+ },
+
+ toString: function(){
+ return this.snippet;
+ },
+
+ toJSON: function(){
+ return this.toString();
+ }
+
+};
+Snippet.escape = function(string){
+ return (''+string).replace(/(?=[$`\\])/,'\\');
+}
+Snippet.escapePlaceholderValue = function(string){
+ return (''+string).replace(/(?=[$`\\}])/,'\\');
+}
+
+Suggestion.fromAPI = function(api, namespace){
+
+ completions.suggestions.push(
+ new Suggestion({
+ tool_tip : api.description +'<hr />'+ (api.notes || ''),
+ match : namespace,
+ display : namespace,
+ image : "Namespace"
+ })
+ .toJSON()
+ );
+
+ if (api.methods && api.methods.length)
+ api.methods.forEach(function(method){
+ // sys.p(method.name, method.returntype, method.parameters.map(function(param){ return param.name+':'+param.type }));
+ completions.suggestions.push(
+ new Suggestion.Method({
+ match : namespace +'.'+ method.name,
+ display : namespace +'.'+ method.name + '\t:' + method.returntype,
+ description: method.value
+ })
+ .addParams(method.parameters)
+ .toJSON()
+ );
+ });
+
+ // if (api.properties && api.properties.length)
+ // api.properties.forEach(function(property){
+ // completions.suggestions.push(
+ // new Suggestion.Property({
+ // tool_tip : property.value,
+ //
+ // match : property.name,
+ // display : property.name + ':' + property.type
+ // })
+ // .toJSON()
+ // );
+ // });
+
+};
+
+
+// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+sys.p(process.env.TM_DIRECTORY + '/api.json');
+
+var API, completions = {
+ extra_chars: "\._$",
+ images: {
+ Namespace:'Namespace.png',
+ Method:'Method.png',
+ Property:'Property.png'
+ },
+ tool_tip_prefix:[
+
+ "<style>"
+ ,'body {background: transparent;}'
+ ,'html,body,table{font-size:10px; font-family: "Lucida Grande", "Trebuchet MS", Verdana, sans-serif;}'
+
+ ,'#root {\
+ background-color: #eef; \
+ background: -webkit-gradient(linear, left top, left bottom, from(#EEE), to(#DDD));\
+ -webkit-border-radius: 1ex; \
+ padding: 1ex 3ex; \
+ -webkit-transition: all 0.75s ease-out;\
+ color: rgba(0,0,0,0.25);\
+ }'
+
+ ,'#root code span { display:block;padding-left:4ex; }'
+ ,'#root code span.info { padding-left:6ex; font-size: 83.3333% }'
+
+ ,'code{font-family:inherit}'
+ ,'code b{ \
+ color: #000; \
+ font-weight:normal; \
+ font-family: "Luxi Mono", Consolas, Monaco, monospace; \
+ text-shadow: #fff 0 1px 2px; \
+ }'
+
+ ,'* {font-size: 100%}'
+ ,'body {font-size: 12px}'
+ ,'#root > p:first-of-type {margin-top:0}'
+ // ,'pre { font-family: Inconsolata, Consolas, Monaco, monospace}'
+ ,"</style>"
+ ,"<script> window.addEventListener('load',function(){"
+ ,"s = document.getElementById('root').style;"
+ ,"s.color = 'rgba(64,64,64,1)';"
+ ,"},false); <\/script>"
+ ,"<div id='root'>"
+
+ ].join(''),
+ suggestions:[]
+};
+
+fs.readFile(process.env.TM_DIRECTORY + '/api.json', function(err, api_json){
+
+ API = JSON.parse(api_json);
+
+ for (var namespace in API){
+ if (!hasOwnProperty.call(API, namespace)) continue;
+ Suggestion.fromAPI(API[namespace], namespace);
+ Suggestion.fromAPI(API[namespace], namespace.replace(/^Titanium/,'Ti'));
+ }
+
+ // sys.p(completions);
+
+ fs.writeFileSync(process.env.TM_DIRECTORY + '/completions.json', JSON.stringify(completions))
+
+});
+
+
+// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+var hasOwnProperty = {}.hasOwnProperty;
+
+
+// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+// -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License
+// -- tlrobinson Tom Robinson
+// dantman Daniel Friesen
+
+/*!
+ Copyright (c) 2009, 280 North Inc. http://280north.com/
+ MIT License. http://github.com/280north/narwhal/blob/master/README.md
+*/
+
+// Brings an environment as close to ECMAScript 5 compliance
+// as is possible with the facilities of erstwhile engines.
+
+// ES5 Draft
+// http://www.ecma-international.org/publications/files/drafts/tc39-2009-050.pdf
+
+// NOTE: this is a draft, and as such, the URL is subject to change. If the
+// link is broken, check in the parent directory for the latest TC39 PDF.
+// http://www.ecma-international.org/publications/files/drafts/
+
+// Previous ES5 Draft
+// http://www.ecma-international.org/publications/files/drafts/tc39-2009-025.pdf
+// This is a broken link to the previous draft of ES5 on which most of the
+// numbered specification references and quotes herein were taken. Updating
+// these references and quotes to reflect the new document would be a welcome
+// volunteer project.
+
+
+// ES5 15.2.3.5
+if (!Object.create) {
+ Object.create = function(prototype, properties) {
+ if (typeof prototype != "object" || prototype === null)
+ throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'");
+ function Type() {};
+ Type.prototype = prototype;
+ var object = new Type();
+ if (typeof properties !== "undefined")
+ Object.defineProperties(object, properties);
+ return object;
+ };
+}
+
View
BIN  Support/icons/Method.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  Support/icons/Namespace.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  Support/icons/Property.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
176 Support/lib/current_word.rb
@@ -0,0 +1,176 @@
+# When creating a custom regex for Word.current_word, you need to keep a few things in mind:
+# The regex matches from your caret, so your caret position will always be the beginning of the line
+# Everything before your caret is reversed for the match, so the left of your caret will also match your caret position as ^
+# You must use a capture group () for the text you're trying to match
+# You must use a capture group () for the text before/after your match
+#
+# EG: /(^[a-z]*)(.*$)/
+# The first capture group in that regex will match from your caret out until it can't find anymore lowercase letters.
+# Then it'll match everything else in the line as the before/after match part.
+# You can currently only use a single regex to match before and after your caret.
+#
+# Since your regex matches what's before your caret in reverse, you'll have to reverse specific stuff in your regex, eg:
+# /(.*):/ wouldn't match 'color' in ` color: `, but /:(.*)/ would.
+# It's reversed you see
+#
+module Word
+ def self.current_word(pat='a-zA-Z0-9', direction=:both)
+ word = ENV['TM_SELECTED_TEXT']
+
+ if word.nil? or word.empty?
+ line, col = ENV['TM_CURRENT_LINE'], ENV['TM_LINE_INDEX'].to_i
+
+ if pat.kind_of? Regexp
+ @reg = pat
+ else
+ @reg = /(^[#{pat}]*)(.*$\r?\n?)/
+ end
+
+ left, before_match = *( line[0...col].reverse.match(@reg) ||[])[1..2]
+ right, after_match = *( line[col..-1] .match(@reg) ||[])[1..2]
+
+ (before_match||='').reverse!
+ (left||='').reverse!
+
+ # p before_match, left, right, after_match
+
+ case direction
+ when :both then word = [left, right].join('')
+ when :left then word = left
+ when :right then word = right
+ when :hash then word = {
+ :line => [before_match, left, right, after_match].join(''),
+ :before_match => before_match,
+ :left => left,
+ :right => right,
+ :after_match => after_match,
+ }
+ end
+ end
+
+ word
+ end
+end
+
+if __FILE__ == $0
+ require "test/unit"
+ class TestWord < Test::Unit::TestCase
+# =begin
+ def test_with_spaces
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ BeforeAfter
+ EOF
+ ENV['TM_LINE_INDEX'] = '10'
+ ENV['TM_TAB_SIZE'] = '2'
+ assert_equal 'BeforeAfter', Word.current_word
+ assert_equal 'Before', Word.current_word('a-zA-Z0-9',:left)
+ assert_equal 'After', Word.current_word('a-zA-Z0-9',:right)
+
+ assert_equal ' Before', Word.current_word(" a-zA-Z",:left)
+ assert_equal 'After ', Word.current_word(" a-zA-Z",:right)
+ end
+
+ def test_with_tabs
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ BeforeAfter
+ EOF
+ ENV['TM_LINE_INDEX'] = '8'
+ ENV['TM_TAB_SIZE'] = '2'
+ assert_equal 'BeforeAfter', Word.current_word
+ assert_equal 'Before', Word.current_word('a-zA-Z0-9',:left)
+ assert_equal 'After', Word.current_word('a-zA-Z0-9',:right)
+
+ assert_equal "\t\tBefore", Word.current_word("\ta-zA-Z",:left)
+ assert_equal "After\t\t", Word.current_word("\ta-zA-Z",:right)
+ end
+
+ def test_with_dash
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ Before--After
+ EOF
+ ENV['TM_LINE_INDEX'] = '11'
+ ENV['TM_TAB_SIZE'] = '2'
+ assert_equal 'Before--After', Word.current_word('-a-zA-Z0-9')
+ assert_equal 'Before-', Word.current_word('-a-zA-Z0-9',:left)
+ assert_equal '-After', Word.current_word('-a-zA-Z0-9',:right)
+
+ assert_equal 'Before-', Word.current_word("\ta-zA-Z\-",:left)
+ end
+
+ def test_hash_result
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ before_match BeforeAfter after_match
+ EOF
+ ENV['TM_LINE_INDEX'] = '22'
+ ENV['TM_TAB_SIZE'] = '2'
+
+ word = Word.current_word("a-zA-Z",:hash)
+
+ assert_equal ENV['TM_CURRENT_LINE'], "#{word[:line]}"
+ assert_equal 'Before', word[:left]
+ assert_equal 'After', word[:right]
+ end
+=begin
+=end
+ def test_both_result
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ before_match BeforeAfter after_match
+ EOF
+ ENV['TM_LINE_INDEX'] = '22'
+ ENV['TM_TAB_SIZE'] = '2'
+
+ assert_equal 'BeforeAfter', Word.current_word("a-zA-Z",:both)
+ end
+
+ def test_should_support_custom_regex
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ before_match BeforeAfter after_match
+ EOF
+ ENV['TM_LINE_INDEX'] = '22'
+ ENV['TM_TAB_SIZE'] = '2'
+
+ assert_equal '', Word.current_word(/[a-zA-Z]/,:both) # No match since no capture group was used!
+
+ assert_equal 'ef', Word.current_word(/([a-z])/,:both) # Capture group, but only selecting a single caracter before and after the caret
+ assert_equal 'eforefter', Word.current_word(/([a-z]+)/,:both) # Only lowercase characters
+ assert_equal 'BeforeAfter', Word.current_word(/^([a-z]*)/i,:both) # Ignore case
+ assert_equal 'BeforeAfter', Word.current_word(/^([a-zA-Z]*)/,:both) # Explicit case
+ end
+
+ def test_should_support_custom_regex_example1
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ background-color: ;
+ EOF
+ ENV['TM_LINE_INDEX'] = '20'
+ ENV['TM_TAB_SIZE'] = '2'
+
+ assert_equal ' background-color: ', Word.current_word(/^(.*)/,:left)
+ assert_equal 'background-color', Word.current_word(/:([-a-z]+)/,:left)
+ end
+
+ def test_should_support_custom_regex_example2
+ ENV['TM_SELECTED_TEXT']= nil
+ ENV['TM_CURRENT_LINE'] = <<-EOF
+ <p class="className" id="flarm">Lorem ipsum dolor sit amet</p>
+ EOF
+ ENV['TM_LINE_INDEX'] = '44'
+ ENV['TM_TAB_SIZE'] = '2'
+
+ assert_equal %Q{\t<p class="className" id="flarm">Lorem ipsum}, Word.current_word(/^(.*)/,:left)
+ assert_equal 'Lorem ipsum dolor sit amet', Word.current_word(/^([^<>]+)/i,:both)
+
+ # You have to reverse the regex since it's matching against the reverse of the text before the caret
+ assert_equal 'p', Word.current_word(/([-:a-z]+)</,:left)
+ # You don't have to reverse your regex when matching text after the caret
+ assert_equal 'p', Word.current_word(/<\/([-:a-z]+)/,:right)
+ end
+
+ end
+end
View
508 Support/lib/tm/complete.rb
@@ -0,0 +1,508 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + "/../current_word"
+require ENV['TM_SUPPORT_PATH'] + '/lib/ui'
+require ENV['TM_SUPPORT_PATH'] + '/lib/escape'
+require ENV['TM_SUPPORT_PATH'] + '/lib/osx/plist'
+require 'rubygems'
+require 'json'
+require 'shellwords'
+
+module TextMate
+ class Complete
+ IMAGES_FOLDER_NAME = 'icons'
+ IMAGES = {
+ "C" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Commands.png",
+ "D" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Drag Commands.png",
+ "L" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Languages.png",
+ "M" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Macros.png",
+ "P" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Preferences.png",
+ "S" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Snippets.png",
+ "T" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Templates.png",
+ "Doc" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Template Files.png",
+ }
+
+ def initialize
+ end
+
+ # 0-config completion command using environment variables for everything
+ def complete!
+ return choices unless choices
+ TextMate::UI.complete(choices, {:images => images, :extra_chars => extra_chars}) do |result|
+ TextMate::UI.tool_tip( result['tool_tip'], {:format => result['tool_tip_format'] || :text })
+ result ? result['insert'] : nil
+ end
+ end
+
+ def tip!
+ # If there is no current_word, then check the next current_word
+ # If there really is no current_word, then show a menu of choices
+
+ chars = "a-zA-Z0-9" # Hard-coded into D2
+ chars += Regexp.escape(extra_chars) if extra_chars
+ current_word ||= Word.current_word chars, :both
+
+ result = nil
+ menu_choices = nil
+ choices = nil
+ choice = 0
+
+ [current_word, current_method_name, current_collection_name].each do |initial_filter|
+ next unless initial_filter and not initial_filter.empty?
+ # p initial_filter
+
+ choices = self.choices.select { |c| (c['match'] || c['display']) =~ /^#{Regexp.quote(initial_filter)}/ }
+
+ # p choices
+ break if choices and not choices.empty?
+ end
+ choices ||= self.choices
+
+ menu_choices = choices.map { |c| c['display'] }
+ choice = TextMate::UI.menu(menu_choices) if menu_choices and menu_choices.length > 1
+ if choice
+ result = choices[choice]
+ end
+ result = {'tool_tip' => 'No information'} unless result
+
+ TextMate::UI.tool_tip( result['tool_tip'], {:format => result['tool_tip_format'] || :text })
+ end
+
+ def choices
+ @choices ||= data['suggestions']
+ end
+ def choices= choice_array
+ @choices = array_to_suggestions(choice_array)
+ end
+
+ def images
+ @images = data['images'] || IMAGES
+
+ data['images']||{}.each_pair do |name,path|
+ @images[name] = path
+ next if File.exists? @images[name]
+ @images[name] = ENV['TM_BUNDLE_SUPPORT'] + "/#{IMAGES_FOLDER_NAME}/" + path
+ next if File.exists? @images[name]
+ @images[name] = ENV['TM_SUPPORT_PATH'] + "/#{IMAGES_FOLDER_NAME}/" + path
+ next if File.exists? @images[name]
+ end
+
+ @images
+ end
+
+ def tool_tip_prefix
+ @tool_tip_prefix ||= data['tool_tip_prefix']
+ end
+
+ def extra_chars
+ ENV['TM_COMPLETIONS_EXTRACHARS'] || data['extra_chars']
+ end
+
+ def chars
+ "a-zA-Z0-9"
+ end
+
+ private
+ def data(raw_data=nil)
+ fix_legacy
+
+ raw_data ||= read_data
+ return {} unless raw_data and not raw_data.empty?
+
+ @data = parse_data raw_data
+ return @data
+ end
+
+ def read_data
+ raw_data = read_file
+ raw_data ||= read_string
+ raw_data
+ end
+
+ def read_file
+ paths = [ENV['TM_COMPLETIONS_FILE']] if ENV['TM_COMPLETIONS_FILE']
+ paths ||= Shellwords.shellwords( ENV['TM_COMPLETIONS_FILES'] ) if ENV.has_key?('TM_COMPLETIONS_FILES')
+ return nil unless paths
+
+ paths.map do |path|
+ next unless path and not path.empty?
+ path = ENV['TM_BUNDLE_SUPPORT'] + '/' + path unless File.exists? path
+ next unless File.exists? path
+
+ { :data => File.read(path),
+ :format => path.scan(/\.([^\.]+)$/).last.last
+ }
+ end
+ end
+
+ def read_string
+ [{:data => ENV['TM_COMPLETIONS'],
+ :format => ENV['TM_COMPLETIONS_SPLIT']
+ }]
+ end
+
+ attr_accessor :filepath
+
+ def parse_data(raw_datas)
+ return @parsed if @parsed
+ parsed = {"suggestions"=>[]}
+
+ raw_datas.each do |raw_data|
+ suggestions = parsed['suggestions']
+
+ case raw_data[:format]
+ when 'plist'
+ par = parse_plist(raw_data)
+ when 'json'
+ par = parse_json(raw_data)
+ when "txt"
+ raw_data[:format] = "\n"
+ par = parse_string(raw_data)
+ when nil
+ raw_data[:format] = ","
+ par = parse_string(raw_data)
+ else
+ par = parse_string(raw_data)
+ end
+
+ if par['tool_tip_prefix']
+ par['suggestions'] = par['suggestions'].map do |suggestion|
+ suggestion['tool_tip'] = par['tool_tip_prefix'] + suggestion['tool_tip']
+ suggestion
+ end
+ end
+
+ parsed.merge! par
+ parsed['suggestions'] = suggestions + parsed['suggestions']
+ end
+
+ @parsed = parsed
+ end
+ def parse_string(raw_data)
+ return {} unless raw_data and raw_data[:data]
+ return raw_data[:data] unless raw_data[:data].respond_to? :to_str
+ raw_data[:data] = raw_data[:data].to_str
+
+ data = {}
+ data['suggestions'] = array_to_suggestions(raw_data[:data].split(raw_data[:format]))
+ data
+ end
+ def parse_plist(raw_data)
+ OSX::PropertyList.load(raw_data[:data])
+ end
+ def parse_json(raw_data)
+ JSON.parse(raw_data[:data])
+ end
+
+ def array_to_suggestions(suggestions)
+ suggestions.delete('')
+
+ suggestions.map! do |c|
+ {'display' => c}
+ end
+
+ suggestions
+ end
+ def current_method_name
+ # Regex for finding a method or function name that's close to your caret position using Word.current_word
+ # TODO: Allow completion prefs to define their own Complete.tip! method_name
+
+ characters = "a-zA-Z0-9" # Hard-coded into D2
+ characters += Regexp.escape(extra_chars) if extra_chars
+
+ regex = %r/
+ (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]* ) \( | )+ \( | )+ \( | )+ \( | )+ \( | )+
+ (?: \(([#{characters}]+) )?/ix
+
+ Word.current_word(regex,:left)
+ end
+ def current_collection_name
+ characters = "a-zA-Z0-9" # Hard-coded into D2
+ characters += Regexp.escape(extra_chars) if extra_chars
+
+ regex = %r/
+ (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]* ) \[ | )+ \[ | )+ \[ | )+ \[ | )+ \[ | )+
+ (?: \[([#{characters}]+) )?/ix
+
+ Word.current_word(regex,:left)
+ end
+
+ def fix_legacy
+ ENV['TM_COMPLETIONS_SPLIT'] ||= ENV['TM_COMPLETIONS_split']
+ end
+ end
+end
+
+if __FILE__ == $0
+
+`open "txmt://open?url=file://$TM_FILEPATH"` #For testing purposes, make this document the topmost so that the complete popup works
+ENV['WEB_PREVIEW_RUBY']='NO-RUN'
+require "test/unit"
+# require "complete"
+
+class TestComplete < Test::Unit::TestCase
+ def setup
+ @string_raw = 'ad(),adipisicing,aliqua,aliquip,amet,anim,aute,cillum,commodo,consectetur,consequat,culpa,cupidatat,deserunt,do,dolor,dolore,Duis,ea,eiusmod,elit,enim,esse,est,et,eu,ex,Excepteur,exercitation,fugiat,id,in,incididunt,ipsum,irure,labore,laboris,laborum,Lorem,magna,minim,mollit,nisi,non,nostrud,nulla,occaecat,officia,pariatur,proident,qui,quis,reprehenderit,sed,sint,sit,sunt,tempor,ullamco,Ut,ut,velit,veniam,voluptate,'
+
+ @plist_raw = <<-'PLIST'
+ { suggestions = (
+ { display = moo; image = Drag; insert = "(${1:one}, ${2:one}, ${3:three}${4:, ${5:five}, ${6:six}})"; tool_tip = "moo(one, two, four[, five])\n This method does something or other maybe.\n Insert longer description of it here."; },
+ { display = foo; image = Macro; insert = "(${1:one}, \"${2:one}\", ${3:three}${4:, ${5:five}, ${6:six}})"; tool_tip = "foo(one, two)\n This method does something or other maybe.\n Insert longer description of it here."; },
+ { display = bar; image = Command; insert = "(${1:one}, ${2:one}, \"${3:three}\"${4:, \"${5:five}\", ${6:six}})"; tool_tip = "bar(one, two[, three])\n This method does something or other maybe.\n Insert longer description of it here."; }
+ );
+ extra_chars = '.';
+ images = {
+ Command = "Commands.png";
+ Drag = "Drag Commands.png";
+ Language = "Languages.png";
+ Macro = "Macros.png";
+ Preference = "Preferences.png";
+ Snippet = "Snippets.png";
+ Template = "Template Files.png";
+ Templates = "Templates.png";
+ };
+ }
+ PLIST
+
+ @json_raw = <<-'JSON'
+ {
+ "extra_chars": "-_$.",
+ "suggestions": [
+ { "display": ".moo", "image": "", "insert": "(${1:one}, ${2:one}, ${3:three}${4:, ${5:five}, ${6:six}})", "tool_tip": "moo(one, two, four[, five])\n This method does something or other maybe.\n Insert longer description of it here." },
+ { "display": "foo", "image": "", "insert": "(${1:one}, \"${2:one}\", ${3:three}${4:, ${5:five}, ${6:six}})", "tool_tip": "foo(one, two)\n This method does something or other maybe.\n Insert longer description of it here." },
+ { "display": "bar", "image": "", "insert": "(${1:one}, ${2:one}, \"${3:three}\"${4:, \"${5:five}\", ${6:six}})", "tool_tip": "bar(one, two[, three])\n This method does something or other maybe.\n Insert longer description of it here." }
+ ],
+ "images": {
+ "String" : "String.png",
+ "RegExp" : "RegExp.png",
+ "Number" : "Number.png",
+ "Array" : "Array.png",
+ "Function": "Function.png",
+ "Object" : "Object.png",
+ "Node" : "Node.png",
+ "NodeList": "NodeList.png"
+ }
+ }
+ JSON
+ end
+
+ def test_basic_complete
+ ENV['TM_COMPLETIONS'] = @string_raw
+
+ assert_equal ENV['TM_COMPLETIONS'].split(','), TextMate::Complete.new.choices.map{|c| c['display']}
+ assert_equal TextMate::Complete::IMAGES, TextMate::Complete.new.images
+
+ TextMate::Complete.new.complete!
+ end
+ #
+ def test_should_support_plist
+ ENV['TM_COMPLETIONS_SPLIT']='plist'
+ ENV['TM_COMPLETIONS'] = @plist_raw
+ TextMate::Complete.new.complete!
+ end
+ #
+ def test_should_support_json
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+
+ ENV['TM_COMPLETIONS_SPLIT']='json'
+ ENV['TM_COMPLETIONS'] = @json_raw
+ fred = TextMate::Complete.new
+ assert_equal(3, fred.choices.length)
+ end
+ #
+ def test_should_be_able_to_modify_the_choices
+ ENV['TM_COMPLETIONS'] = @string_raw
+
+ fred = TextMate::Complete.new
+
+ assert_not_nil fred.choices
+ assert_equal ENV['TM_COMPLETIONS'].split(','), fred.choices.map{|c| c['display']}
+ fred.choices.reject!{|choice| choice['display'] !~ /^a/ }
+ assert_equal ENV['TM_COMPLETIONS'].split(',').grep(/^a/), fred.choices.map{|c| c['display']}
+
+ fred.choices=%w[fred is not my name]
+ assert_equal %w[fred is not my name], fred.choices.map{|c| c['display']}
+ end
+ #
+ def test_should_parse_files_based_on_extension_plist
+ ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.plist'
+
+ File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @plist_raw }
+ assert File.exists?(ENV['TM_COMPLETIONS_FILE'])
+
+ fred = TextMate::Complete.new
+ assert_equal(['moo', 'foo', 'bar'], fred.choices.map{|c| c['display']})
+ end
+ #
+ def test_should_parse_files_based_on_extension_txt
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+
+ ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.txt'
+
+ File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @string_raw.gsub(',',"\n") }
+ assert File.exists?(ENV['TM_COMPLETIONS_FILE'])
+
+ fred = TextMate::Complete.new
+
+ assert_equal(@string_raw.split(','), fred.choices.map{|c| c['display']})
+ end
+ #
+ def test_should_parse_multiple_files
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+ ENV.delete 'TM_COMPLETIONS_FILE'
+ assert_nil(ENV['TM_COMPLETIONS_FILE'])
+
+ ENV['TM_COMPLETIONS_FILES'] = "'/tmp/completions_test.txt' '/tmp/completions_test1.txt' '/tmp/completions_test2.txt'"
+
+ require 'shellwords'
+ Shellwords.shellwords( ENV['TM_COMPLETIONS_FILES'] ).each_with_index do |filepath,i|
+ File.open(filepath,'w'){|file| file.write @string_raw.gsub(',',"#{i}\n") }
+ assert File.exists?(filepath)
+ end
+
+ fred = TextMate::Complete.new
+
+ assert_equal(@string_raw.split(',').uniq.length * 3, fred.choices.map{|c| c['display']}.length )
+ end
+ #
+ def test_should_override_split_with_extension
+ ENV['TM_COMPLETIONS_SPLIT'] = ','
+ ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.plist'
+
+ File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @plist_raw }
+ assert File.exists?(ENV['TM_COMPLETIONS_FILE'])
+
+ fred = TextMate::Complete.new
+ assert_equal(['moo', 'foo', 'bar'], fred.choices.map{|c| c['display']})
+ end
+ #
+ def test_should_get_extra_chars_from_var
+ ENV['TM_COMPLETIONS_SPLIT']=','
+ ENV['TM_COMPLETIONS'] = @string_raw
+ ENV['TM_COMPLETIONS_EXTRACHARS'] = '.'
+
+ fred = TextMate::Complete.new
+ assert_equal('.', fred.extra_chars)
+ end
+ #
+ def test_should_get_extra_chars_from_plist
+ ENV['TM_COMPLETIONS_SPLIT']='plist'
+ ENV['TM_COMPLETIONS'] = @plist_raw
+
+ assert_nil(ENV['TM_COMPLETIONS_EXTRACHARS'])
+
+ fred = TextMate::Complete.new
+ assert_equal('.', fred.extra_chars)
+ end
+ # TODO: should_fix_image_paths
+# =begin
+ def test_should_fix_image_paths
+ ENV['TM_COMPLETIONS_SPLIT'] = 'plist'
+ ENV['TM_COMPLETIONS'] = @plist_raw
+ ENV['TM_BUNDLE_SUPPORT'] = '/tmp'
+ ENV['TM_SUPPORT_PATH'] = '/tmp'
+
+ images = OSX::PropertyList.load(@plist_raw)['images']
+
+ FileUtils.mkdir_p "#{ENV['TM_SUPPORT_PATH']}/#{TextMate::Complete::IMAGES_FOLDER_NAME}"
+ images.each_pair do |name,path|
+ File.open("#{ENV['TM_SUPPORT_PATH']}/#{TextMate::Complete::IMAGES_FOLDER_NAME}/#{path}", 'w'){ |file| file.write('') }
+ end
+
+ TextMate::Complete.new.images.each_pair do |name,path|
+ p path
+ assert File.exists?(path)
+ end
+
+ end
+=begin
+
+=end
+ def test_should_apply_prefix
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+
+ @json_raw = <<-'JSON'
+ {
+ "extra_chars": "-_$.",
+ "tool_tip_prefix":"prefix",
+ "suggestions": [
+ { "display": ".moo", "image": "", "insert": "(${1:one}, ${2:one}, ${3:three}${4:, ${5:five}, ${6:six}})", "tool_tip": "moo(one, two, four[, five])\n This method does something or other maybe.\n Insert longer description of it here." },
+ { "display": "foo", "image": "", "insert": "(${1:one}, \"${2:one}\", ${3:three}${4:, ${5:five}, ${6:six}})", "tool_tip": "foo(one, two)\n This method does something or other maybe.\n Insert longer description of it here." },
+ { "display": "bar", "image": "", "insert": "(${1:one}, ${2:one}, \"${3:three}\"${4:, \"${5:five}\", ${6:six}})", "tool_tip": "bar(one, two[, three])\n This method does something or other maybe.\n Insert longer description of it here." }
+ ],
+ "images": {
+ "String" : "String.png",
+ "RegExp" : "RegExp.png",
+ "Number" : "Number.png",
+ "Array" : "Array.png",
+ "Function": "Function.png",
+ "Object" : "Object.png",
+ "Node" : "Node.png",
+ "NodeList": "NodeList.png"
+ }
+ }
+ JSON
+
+ ENV['TM_COMPLETIONS_SPLIT']='json'
+ ENV['TM_COMPLETIONS'] = @json_raw
+ fred = TextMate::Complete.new
+ assert_equal(3, fred.choices.length)
+ assert fred.choices.first['tool_tip'].match(/^prefix/)
+ end
+ #
+ def test_should_show_tooltip_without_inserting_anything
+ # This method passes if it shows a tooltip when selecting a menu-item
+ # and DOESN'T insert anything or cause the document think anything has changed
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+
+ ENV['TM_COMPLETIONS_SPLIT']='json'
+ ENV['TM_COMPLETIONS'] = @json_raw
+ fred = TextMate::Complete.new
+ assert_equal(3, fred.choices.length)
+
+ TextMate::Complete.new.tip!
+ end
+ #
+ def test_tip_should_look_for_the_current_word_and_then_try_the_closest_function_name
+ ENV.delete 'TM_COMPLETIONS'
+ assert_nil(ENV['TM_COMPLETIONS'])
+ ENV.delete 'TM_COMPLETIONS_SPLIT'
+ assert_nil(ENV['TM_COMPLETIONS_SPLIT'])
+
+ ENV['TM_COMPLETIONS_SPLIT']='json'
+ ENV['TM_COMPLETIONS'] = @json_raw
+ fred = TextMate::Complete.new
+ assert_equal(3, fred.choices.length)
+
+ TextMate::Complete.new.tip!
+ #
+ # This test passes if, when run, you see the tooltip for closest function
+ # Be sure to more your caret around and try a few times
+ # Showing a menu is a fail
+ #
+ # foo( bar( ), '.moo' )
+ # ^ Caret here should give the tip for 'foo'
+ # ^ Caret here should give the tip for 'bar'
+ # ^ Caret here should give the tip for 'bar'
+ # ^ Caret here should give the tip for '.moo'
+ # foo( bar(one,two,foo), '.moo' )
+ # foo['bar']
+ end
+ #
+end
+
+end#if
View
46 Syntaxes/JavaScript Titanium Mobile.tmLanguage
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>fileTypes</key>
+ <array>
+ <string>ti.js</string>
+ <string>js</string>
+ </array>
+ <key>foldingStartMarker</key>
+ <string>(?x)^
+ # Yes *+ Open ( Blank |Comments | (No Paren | String) | String | (Nested Parens ) )*+
+ (?&gt; \g&lt;yesS&gt;*+ \[ (?&lt;yesS&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noS&gt;[^\[\]'"/]) | (?&lt;str&gt; '(\\'|[^'])*+' | "(\\"|[^"])*+" | /(\\/|[^/])*+/) | (?&lt;squar&gt; \[ \g&lt;yesS&gt;*+ \] ) )*+
+ | \g&lt;yesR&gt;*+ \( (?&lt;yesR&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noR&gt;[^\(\)'"/]) | \g&lt;str&gt; | (?&lt;round&gt; \( \g&lt;yesR&gt;*+ \) ) )*+
+ | \g&lt;yesC&gt;*+ \{ (?&lt;yesC&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noC&gt;[^\{\}'"/]) | \g&lt;str&gt; | (?&lt;curly&gt; \{ \g&lt;yesC&gt;*+ \} ) )*+
+ )$</string>
+ <key>foldingStopMarker</key>
+ <string>(?x)^
+ # ( Blank |Comments | (No Paren | String) | String | (Nested Parens ) )*+ Close Yes *+
+ (?&gt; (?&lt;yesS&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noS&gt;[^\[\]'"/]) | (?&lt;str&gt; '(\\'|[^'])*+' | "(\\"|[^"])*+" | /(\\/|[^/])*+/) | (?&lt;squar&gt; \[ \g&lt;yesS&gt;*+ \] ) )*+ \] \g&lt;yesS&gt;*+
+ | (?&lt;yesR&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noR&gt;[^\(\)'"/]) | \g&lt;str&gt; | (?&lt;round&gt; \( \g&lt;yesR&gt;*+ \) ) )*+ \) \g&lt;yesR&gt;*+
+ | (?&lt;yesC&gt; (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?&lt;noC&gt;[^\{\}'"/]) | \g&lt;str&gt; | (?&lt;curly&gt; \{ \g&lt;yesC&gt;*+ \} ) )*+ \} \g&lt;yesC&gt;*+
+ )$</string>
+ <key>keyEquivalent</key>
+ <string>^~J</string>
+ <key>name</key>
+ <string>Titanium Mobile JavaScript</string>
+ <key>patterns</key>
+ <array>
+ <dict>
+ <key>match</key>
+ <string>(?&lt;=Ti)\s</string>
+ <key>name</key>
+ <string>meta.complete.method.ti</string>
+ </dict>
+ <dict>
+ <key>include</key>
+ <string>source.js</string>
+ </dict>
+ </array>
+ <key>scopeName</key>
+ <string>source.js.ti.mobile</string>
+ <key>uuid</key>
+ <string>CB708386-8483-491A-AE9A-A279EA0E17CC</string>
+</dict>
+</plist>
View
11 info.plist
@@ -4,6 +4,17 @@
<dict>
<key>name</key>
<string>JavaScript Appcelerator Titanium Mobile</string>
+ <key>ordering</key>
+ <array>
+ <string>1B66F6F3-D2F3-4324-A11E-E0E5F8670AD7</string>
+ <string>E4384F6B-629C-4F74-A011-59D3BF40D2B5</string>
+ <string>59FF1465-9977-49BE-97F8-3205CBF011C5</string>
+ <string>88506B58-5A8F-4009-8F5A-AC39182378EB</string>
+ <string>E910E64B-7423-4B47-8F18-1847D4DEE85B</string>
+ <string>CB708386-8483-491A-AE9A-A279EA0E17CC</string>
+ <string>602D47B3-1101-483B-A11E-86550C77E229</string>
+ <string>1BE04E27-BDCF-4B7D-B7A5-479D0478689F</string>
+ </array>
<key>uuid</key>
<string>3F2569A1-DA0B-43CC-B968-A402EC3C1079</string>
</dict>
Please sign in to comment.
Something went wrong with that request. Please try again.