├── Snippets ├── include().tmSnippet ├── Ti_API_info.tmSnippet ├── Ti_API_debug.tmSnippet ├── Ti_App_fireEvent.tmSnippet ├── Ti_API_log.tmSnippet └── Ti_App_addEventListener.tmSnippet ├── Commands ├── Xcode Debug.tmCommand ├── Code Completion.tmCommand ├── Documentation for Word : Selection (tool tip).tmCommand └── Appcelerator Titanium Mobile Developer Center.tmCommand ├── info.plist ├── Support ├── tool_tip.template.html ├── generateCompletions.js ├── lib │ ├── Snippet.js │ ├── string.js │ ├── current_word.rb │ ├── Suggestion.js │ ├── tm │ │ └── complete.rb │ └── global-es5.js └── generateCompletionsArray.js ├── README.md ├── Macros └── Ti_ Completion.tmMacro └── Syntaxes └── JavaScript Titanium Mobile.tmLanguage /Snippets/include().tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.include($0); 7 | name 8 | Ti.include() 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | inc 13 | uuid 14 | 602D47B3-1101-483B-A11E-86550C77E229 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/Ti_API_info.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.API.info(${0:${TM_SELECTED_TEXT}}) 7 | name 8 | Ti.API.info 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | i 13 | uuid 14 | 24CE4EC5-A5E7-4779-BABB-7C433746D6A1 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/Ti_API_debug.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.API.debug(${0:${TM_SELECTED_TEXT}}) 7 | name 8 | Ti.API.debug 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | d 13 | uuid 14 | 1BE04E27-BDCF-4B7D-B7A5-479D0478689F 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/Ti_App_fireEvent.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.App.fireEvent('${1:event}'$0) 7 | name 8 | Ti.App.fireEvent 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | fe 13 | uuid 14 | A64DAC04-44C8-460A-AB37-4E59D72CC93E 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/Ti_API_log.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.API.log('${1:info}',${0:${TM_SELECTED_TEXT}}) 7 | name 8 | Ti.API.log 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | l 13 | uuid 14 | 72501E12-8711-4DB8-B04C-349D2675F5E6 15 | 16 | 17 | -------------------------------------------------------------------------------- /Snippets/Ti_App_addEventListener.tmSnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | content 6 | Ti.App.addEventListener('${1:event}', $0); 7 | name 8 | Ti.App.addEventListener 9 | scope 10 | source.js.ti 11 | tabTrigger 12 | ae 13 | uuid 14 | C1B975FE-E24C-481D-B032-8784FBC75658 15 | 16 | 17 | -------------------------------------------------------------------------------- /Commands/Xcode Debug.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | osascript -e 'tell the application "Xcode" to debug' &>/dev/null & 9 | 10 | input 11 | none 12 | keyEquivalent 13 | @r 14 | name 15 | Xcode Debug 16 | output 17 | discard 18 | scope 19 | source.js.ti 20 | uuid 21 | 0D82570E-96A6-4C42-9CC2-52A166728507 22 | 23 | 24 | -------------------------------------------------------------------------------- /Commands/Code Completion.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete' 10 | TextMate::Complete.new.complete! 11 | 12 | input 13 | none 14 | keyEquivalent 15 | ~ 16 | name 17 | Code Completion 18 | output 19 | showAsTooltip 20 | scope 21 | source.js.ti 22 | uuid 23 | 88506B58-5A8F-4009-8F5A-AC39182378EB 24 | 25 | 26 | -------------------------------------------------------------------------------- /Commands/Documentation for Word : Selection (tool tip).tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | #!/usr/bin/env ruby 9 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete' 10 | TextMate::Complete.new.tip! 11 | 12 | fallbackInput 13 | word 14 | input 15 | none 16 | keyEquivalent 17 | ~ 18 | name 19 | Documentation for Word / Selection (tool tip) 20 | output 21 | showAsTooltip 22 | scope 23 | source.js.ti 24 | uuid 25 | 59FF1465-9977-49BE-97F8-3205CBF011C5 26 | 27 | 28 | -------------------------------------------------------------------------------- /Commands/Appcelerator Titanium Mobile Developer Center.tmCommand: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | beforeRunningCommand 6 | nop 7 | command 8 | exit_show_html "<meta http-equiv=Refresh content='0;URL=http://developer.appcelerator.com/apidoc/search/mobile/1.5.1?q=${TM_SELECTED_TEXT:-$TM_CURRENT_WORD}'>" 9 | 10 | fallbackInput 11 | word 12 | input 13 | selection 14 | keyEquivalent 15 | ^h 16 | name 17 | Appcelerator Titanium Mobile Developer Center 18 | output 19 | showAsTooltip 20 | scope 21 | source.js.ti.mobile 22 | uuid 23 | E910E64B-7423-4B47-8F18-1847D4DEE85B 24 | 25 | 26 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | JavaScript Appcelerator Titanium Mobile 7 | ordering 8 | 9 | 1B66F6F3-D2F3-4324-A11E-E0E5F8670AD7 10 | E4384F6B-629C-4F74-A011-59D3BF40D2B5 11 | 59FF1465-9977-49BE-97F8-3205CBF011C5 12 | 88506B58-5A8F-4009-8F5A-AC39182378EB 13 | E910E64B-7423-4B47-8F18-1847D4DEE85B 14 | CB708386-8483-491A-AE9A-A279EA0E17CC 15 | 602D47B3-1101-483B-A11E-86550C77E229 16 | 1BE04E27-BDCF-4B7D-B7A5-479D0478689F 17 | 24CE4EC5-A5E7-4779-BABB-7C433746D6A1 18 | 72501E12-8711-4DB8-B04C-349D2675F5E6 19 | A64DAC04-44C8-460A-AB37-4E59D72CC93E 20 | C1B975FE-E24C-481D-B032-8784FBC75658 21 | 22 | uuid 23 | 3F2569A1-DA0B-43CC-B968-A402EC3C1079 24 | 25 | 26 | -------------------------------------------------------------------------------- /Support/tool_tip.template.html: -------------------------------------------------------------------------------- 1 | 46 | 56 |
57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Appcelerator Titanium Mobile JavaScript TextMate Bundle 2 | ==== 3 | By [Thomas Aylott](http://SubtleGradient.com) 4 | 5 | *MIT License* 6 | 7 | How to install 8 | ---- 9 | 10 | 0. Install TextMate (um… duh?) 11 | 1. Download [JavaScript-Appcelerator-Titanium-Mobile.tmbundle.zip](http://github.com/subtleGradient/JavaScript-Appcelerator-Titanium-Mobile.tmbundle/zipball/master) 12 | 2. Unzip 13 | 3. Rename the folder to `ti-mo.tmbundle` 14 | * The name of the folder doesn't really matter, just so long as it has the `tmbundle` extension 15 | 4. Double-Click 16 | 5. Install the JSON Ruby Gem 17 | * `sudo gem install json` 18 | 6. Enjoy! 19 | 20 | 21 | How to use 22 | --- 23 | 24 | ### Complete! ⌥⎋ *option-escape* 25 | The completion list contains all of the `Titanium` and `Ti` API. 26 | 27 | ### Tooltips! ⌥F1 28 | The tips are context sensitive. Place your caret anywhere inside of a function (on the same line) and it'll work, even inside the arguments `()`! 29 | 30 | ### Complete! Type `Ti.` (optional) 31 | *Enable this by selecting the "JavaScript Titanium Mobile" language in the bottom bar of TextMate.* 32 | 33 | As soon as you hit the `.` (after typing `Ti`) it'll bring up the as-you-type completion list thing. 34 | 35 | Note: If you are at the last line and click "." then the code completion list won't appear. You must leave a blank line at the end of your document. 36 | -------------------------------------------------------------------------------- /Macros/Ti_ Completion.tmMacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | commands 6 | 7 | 8 | argument 9 | . 10 | command 11 | insertText: 12 | 13 | 14 | argument 15 | 16 | beforeRunningCommand 17 | nop 18 | command 19 | #!/usr/bin/env ruby 20 | require ENV['TM_BUNDLE_SUPPORT'] + '/lib/tm/complete' 21 | TextMate::Complete.new.complete! 22 | 23 | input 24 | none 25 | keyEquivalent 26 | ~ 27 | name 28 | Code Completion 29 | output 30 | showAsTooltip 31 | scope 32 | source.js.ti 33 | uuid 34 | 88506B58-5A8F-4009-8F5A-AC39182378EB 35 | 36 | command 37 | executeCommandWithOptions: 38 | 39 | 40 | keyEquivalent 41 | . 42 | name 43 | Ti. Completion 44 | scope 45 | meta.complete.method.ti 46 | uuid 47 | 1B66F6F3-D2F3-4324-A11E-E0E5F8670AD7 48 | 49 | 50 | -------------------------------------------------------------------------------- /Support/generateCompletions.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | --- 4 | name : generateCompletions 5 | description : generateCompletions generates Completions 6 | 7 | authors : Thomas Aylott 8 | copyright : © 2010 Thomas Aylott 9 | license : MIT 10 | 11 | requires : 12 | - Node.js/require 13 | - Node.js/process.env 14 | - Node.js/sys 15 | - Node.js/fs 16 | - Suggestion 17 | ... 18 | */ 19 | 20 | var ROOT_DIR = process.env.TM_DIRECTORY || process.env.PWD; 21 | 22 | var hasOwnProperty = {}.hasOwnProperty; 23 | 24 | var sys = require('sys'); 25 | var fs = require('fs'); 26 | 27 | require.paths.unshift('./lib'); 28 | 29 | var Suggestion = require('Suggestion').Suggestion; 30 | 31 | // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 32 | 33 | Suggestion.completions.tool_tip_prefix = require('fs').readFileSync(ROOT_DIR + '/tool_tip.template.html'); 34 | 35 | var API = JSON.parse(fs.readFileSync(ROOT_DIR + '/api.json')); 36 | 37 | for (var namespace in API){ 38 | if (!hasOwnProperty.call(API, namespace)) continue; 39 | 40 | Suggestion.fromAPI(API[namespace], namespace, API); 41 | Suggestion.fromAPI(API[namespace], namespace.replace(/^Titanium/,'Ti'), API); 42 | } 43 | 44 | addMissingStuff(); 45 | 46 | fs.writeFileSync(ROOT_DIR + '/completions.json', JSON.stringify(Suggestion.completions)) 47 | 48 | sys.print('Done!') 49 | 50 | // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 51 | 52 | function addMissingStuff(){ 53 | // currentWindow 54 | // currentTab 55 | // currentTabGroup 56 | } 57 | -------------------------------------------------------------------------------- /Support/lib/Snippet.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name : Snippet 4 | description : Create TextMate / Espresso style snippets using a simple API! 5 | version: 0.1 6 | 7 | authors : Thomas Aylott 8 | copyright : © 2010 Thomas Aylott 9 | license : MIT 10 | 11 | provides : Snippet 12 | ... 13 | */ 14 | 15 | function Snippet(string){ 16 | if (!(this instanceof Snippet)) return new Snippet(string); 17 | 18 | this.snippet = ''; 19 | this.appendText(string || ''); 20 | this.placeholderID = 0; 21 | } 22 | 23 | Snippet.prototype = { 24 | 25 | appendRaw: function(string){ 26 | this.snippet += string; 27 | return this; 28 | }, 29 | 30 | appendText: function(string){ 31 | this.snippet += Snippet[this.insidePlaceholder ? 'escapePlaceholderValue' : 'escape'](string); 32 | return this; 33 | }, 34 | 35 | addPlaceholder: function(value, escape){ 36 | this.appendPlaceholder(++ this.placeholderID, value, escape); 37 | return this; 38 | }, 39 | 40 | addLast: function(value, escape){ 41 | this.appendPlaceholder(0, value, escape); 42 | return this; 43 | }, 44 | 45 | appendPlaceholder: function(key, value, escape){ 46 | if (typeof escape === 'undefined') escape = true; 47 | if (!value) value = ''; 48 | if (escape) value = Snippet.escapePlaceholderValue(value); 49 | this.snippet += "${"+ key +":"+ value +"}"; 50 | return this; 51 | }, 52 | 53 | begin: function(key){ 54 | if (key == null) key = ++ this.placeholderID; 55 | this.snippet += "${"+ key +":"; 56 | this.insidePlaceholder = true; 57 | return this; 58 | }, 59 | 60 | end: function(){ 61 | this.snippet += "}"; 62 | this.insidePlaceholder = false; 63 | return this; 64 | }, 65 | 66 | toString: function(){ 67 | return this.snippet; 68 | }, 69 | 70 | toJSON: function(){ 71 | return this.toString(); 72 | } 73 | 74 | }; 75 | 76 | Snippet.escape = function(string){ 77 | return (''+string).replace(/(?=[$`\\])/,'\\'); 78 | } 79 | 80 | Snippet.escapePlaceholderValue = function(string){ 81 | return (''+string).replace(/(?=[$`\\}])/,'\\'); 82 | } 83 | 84 | 85 | if (typeof exports == 'undefined') var exports = {}; 86 | exports.Snippet = Snippet; 87 | 88 | -------------------------------------------------------------------------------- /Support/generateCompletionsArray.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | --- 4 | name : generateCompletions 5 | description : generateCompletions generates Completions 6 | 7 | authors : Thomas Aylott 8 | copyright : © 2010 Thomas Aylott 9 | license : MIT 10 | 11 | requires : 12 | - Node.js/require 13 | - Node.js/process.env 14 | - Node.js/sys 15 | - Node.js/fs 16 | - Suggestion 17 | ... 18 | */ 19 | 20 | var ROOT_DIR = process.env.TM_DIRECTORY || process.env.PWD; 21 | 22 | var hasOwnProperty = {}.hasOwnProperty; 23 | 24 | var sys = require('sys'); 25 | var fs = require('fs'); 26 | 27 | require.paths.unshift('./lib'); 28 | 29 | function push(it){ this[it] = true } 30 | 31 | var completions = {}; 32 | // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 33 | 34 | /* 35 | curl -s 'http://developer.appcelerator.com/apidoc/mobile/1.5.1/api.json' > "$(dirname "$TM_FILEPATH")/api.json" 36 | */ 37 | 38 | var API = JSON.parse(fs.readFileSync(ROOT_DIR + '/api.json')); 39 | 40 | for (var namespace in API){ 41 | if (!hasOwnProperty.call(API, namespace)) continue; 42 | 43 | push.call(completions, namespace) 44 | push.call(completions, namespace.replace('tanium','')) 45 | 46 | for (var i = 0; i < API[namespace].methods.length; ++i){ 47 | push.call(completions, API[namespace].methods[i].name) 48 | // push.call(completions, namespace + '.' + API[namespace].methods[i].name) 49 | // push.call(completions, namespace.replace('tanium','') + '.' + API[namespace].methods[i].name) 50 | 51 | push.call(completions, API[namespace].methods[i].name+'()') 52 | push.call(completions, namespace + '.' + API[namespace].methods[i].name+'()') 53 | push.call(completions, namespace.replace('tanium','') + '.' + API[namespace].methods[i].name+'()') 54 | } 55 | 56 | for (var i = 0; i < API[namespace].properties.length; ++i){ 57 | push.call(completions, API[namespace].properties[i].name) 58 | push.call(completions, namespace + '.' + API[namespace].properties[i].name) 59 | push.call(completions, namespace.replace('tanium','') + '.' + API[namespace].properties[i].name) 60 | } 61 | } 62 | sys.puts(( '' + Object.keys(completions).sort() .join('\n') + '' )) 63 | // fs.writeFileSync(ROOT_DIR + '/completions.json', JSON.stringify(completions)) 64 | 65 | // sys.print('Done!') 66 | 67 | -------------------------------------------------------------------------------- /Syntaxes/JavaScript Titanium Mobile.tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fileTypes 6 | 7 | ti.js 8 | js 9 | 10 | foldingStartMarker 11 | (?x)^ 12 | # Yes *+ Open ( Blank |Comments | (No Paren | String) | String | (Nested Parens ) )*+ 13 | (?> \g<yesS>*+ \[ (?<yesS> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noS>[^\[\]'"/]) | (?<str> '(\\'|[^'])*+' | "(\\"|[^"])*+" | /(\\/|[^/])*+/) | (?<squar> \[ \g<yesS>*+ \] ) )*+ 14 | | \g<yesR>*+ \( (?<yesR> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noR>[^\(\)'"/]) | \g<str> | (?<round> \( \g<yesR>*+ \) ) )*+ 15 | | \g<yesC>*+ \{ (?<yesC> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noC>[^\{\}'"/]) | \g<str> | (?<curly> \{ \g<yesC>*+ \} ) )*+ 16 | )$ 17 | foldingStopMarker 18 | (?x)^ 19 | # ( Blank |Comments | (No Paren | String) | String | (Nested Parens ) )*+ Close Yes *+ 20 | (?> (?<yesS> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noS>[^\[\]'"/]) | (?<str> '(\\'|[^'])*+' | "(\\"|[^"])*+" | /(\\/|[^/])*+/) | (?<squar> \[ \g<yesS>*+ \] ) )*+ \] \g<yesS>*+ 21 | | (?<yesR> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noR>[^\(\)'"/]) | \g<str> | (?<round> \( \g<yesR>*+ \) ) )*+ \) \g<yesR>*+ 22 | | (?<yesC> (?!.) |//.*+|/\*((?!\*/).)++(\*/|$)|(^)((?!/\*).)*(\*/)| (?<noC>[^\{\}'"/]) | \g<str> | (?<curly> \{ \g<yesC>*+ \} ) )*+ \} \g<yesC>*+ 23 | )$ 24 | keyEquivalent 25 | ^~J 26 | name 27 | Titanium Mobile JavaScript 28 | patterns 29 | 30 | 31 | match 32 | (?<=Ti)\s 33 | name 34 | meta.complete.method.ti 35 | 36 | 37 | include 38 | source.js 39 | 40 | 41 | scopeName 42 | source.js.ti.mobile 43 | uuid 44 | CB708386-8483-491A-AE9A-A279EA0E17CC 45 | 46 | 47 | -------------------------------------------------------------------------------- /Support/lib/string.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name : string 4 | description : string extensions 5 | 6 | authors : Thomas Aylott 7 | copyright : © 2010 Thomas Aylott 8 | license : MIT 9 | 10 | provides : 11 | - String.prototype.ljust 12 | - String.prototype.multiply 13 | ... 14 | */ 15 | 16 | // http://code.google.com/p/zaapaas/source/browse/trunk/reform/util.js 17 | // util.js - utility functions 18 | // 19 | // Copyright (C) 2009 Mikey K. 20 | 21 | //////////////////////////////////////////////////////////////////////// 22 | // Various Prototype Extensions 23 | //////////////////////////////////////////////////////////////////////// 24 | 25 | if (!String.prototype.ljust) 26 | String.prototype.ljust = function(len, chr){ 27 | if (len > this.length) 28 | return this + (chr ? chr : " ").multiply(len - this.length); 29 | else 30 | return ''+this; 31 | }; 32 | 33 | if (!String.prototype.rjust) 34 | String.prototype.rjust = function(len, chr){ 35 | if (len > this.length) 36 | return (chr ? chr : " ").multiply(len - this.length) + this; 37 | else 38 | return ''+this; 39 | }; 40 | 41 | // Copyright 2010 Thomas Aylott; MIT License 42 | if (!String.prototype.cjust) 43 | String.prototype.cjust = function(length, chr){ 44 | if (!chr) chr = " "; 45 | if (length > this.length){ 46 | var padding = chr.multiply(length / 2 - this.length); 47 | return (padding + this + padding).ljust(length,chr); 48 | } 49 | else return ''+this; 50 | }; 51 | 52 | //////////////////////////////////////////////////////////////////////// 53 | // Kris Kowal 54 | // http://blog.stevenlevithan.com/archives/fast-string-multiply 55 | //////////////////////////////////////////////////////////////////////// 56 | 57 | if (!String.prototype.multiply) 58 | String.prototype.multiply = function(num){ 59 | num = Math.round(num); 60 | var str = this; 61 | var acc = []; 62 | for (var i = 0; (1 << i) <= num; i++) { 63 | if ((1 << i) & num) 64 | acc.push(str); 65 | str += str; 66 | } 67 | return acc.join(""); 68 | } 69 | 70 | // -- 71 | 72 | if (typeof console != 'undefined' && console.assert) { 73 | 74 | console.assert(".".multiply(3) === "...", ".".multiply(3)); 75 | console.assert(".".multiply(1.5) === "..", ".".multiply(1.5)); 76 | 77 | console.assert("1".ljust(1) === "1", "-1".ljust(1)); 78 | 79 | console.assert("1".ljust(3) === "1 ", "1".ljust(3)); 80 | console.assert("1".rjust(3) === " 1", "1".rjust(3)); 81 | console.assert("1".cjust(3) === " 1 ", "1".cjust(3)); 82 | 83 | console.assert("1".ljust(3,'.') === "1..", "1".ljust(3,'.')); 84 | console.assert("1".rjust(3,'.') === "..1", "1".rjust(3,'.')); 85 | console.assert("1".cjust(3,'.') === ".1.", "1".cjust(3,'.')); 86 | 87 | console.assert("2".ljust(4,'.') === "2...", "2".ljust(4,'.')); 88 | console.assert("2".rjust(4,'.') === "...2", "2".rjust(4,'.')); 89 | console.assert("2".cjust(4,'.') === ".2..", "2".cjust(4,'.')); 90 | 91 | console.assert("1".ljust(4) === "1 ", "1".ljust(4)); 92 | console.assert("1".rjust(4) === " 1", "1".rjust(4)); 93 | console.assert("1".cjust(4) === " 1 ", "1".cjust(4)); 94 | 95 | console.assert("1".ljust(16)==="1 ","1".ljust(16)); 96 | console.assert("1".rjust(16)===" 1","1".rjust(16)); 97 | console.assert("1".cjust(16)===" 1 ","1".cjust(16)); 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Support/lib/current_word.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | --- 3 | url: http://gist.github.com/437000 4 | ... 5 | =end 6 | # When creating a custom regex for Word.current_word, you need to keep a few things in mind: 7 | # The regex matches from your caret, so your caret position will always be the beginning of the line 8 | # Everything before your caret is reversed for the match, so the left of your caret will also match your caret position as ^ 9 | # You must use a capture group () for the text you're trying to match 10 | # You must use a capture group () for the text before/after your match 11 | # 12 | # EG: /(^[a-z]*)(.*$)/ 13 | # The first capture group in that regex will match from your caret out until it can't find anymore lowercase letters. 14 | # Then it'll match everything else in the line as the before/after match part. 15 | # You can currently only use a single regex to match before and after your caret. 16 | # 17 | # Since your regex matches what's before your caret in reverse, you'll have to reverse specific stuff in your regex, eg: 18 | # /(.*):/ wouldn't match 'color' in ` color: `, but /:(.*)/ would. 19 | # It's reversed you see 20 | # 21 | module Word 22 | def self.current_word(pat='a-zA-Z0-9', direction=:both) 23 | word = ENV['TM_SELECTED_TEXT'] 24 | 25 | if word.nil? or word.empty? 26 | line, col = ENV['TM_CURRENT_LINE'], ENV['TM_LINE_INDEX'].to_i 27 | 28 | if pat.kind_of? Regexp 29 | @reg = pat 30 | else 31 | @reg = /(^[#{pat}]*)(.*$\r?\n?)/ 32 | end 33 | 34 | left, before_match = *( line[0...col].reverse.match(@reg) ||[])[1..2] 35 | right, after_match = *( line[col..-1] .match(@reg) ||[])[1..2] 36 | 37 | (before_match||='').reverse! 38 | (left||='').reverse! 39 | 40 | # p before_match, left, right, after_match 41 | 42 | case direction 43 | when :both then word = [left, right].join('') 44 | when :left then word = left 45 | when :right then word = right 46 | when :hash then word = { 47 | :line => [before_match, left, right, after_match].join(''), 48 | :before_match => before_match, 49 | :left => left, 50 | :right => right, 51 | :after_match => after_match, 52 | } 53 | end 54 | end 55 | 56 | word 57 | end 58 | end 59 | 60 | if __FILE__ == $0 61 | require "test/unit" 62 | class TestWord < Test::Unit::TestCase 63 | # =begin 64 | def test_with_spaces 65 | ENV['TM_SELECTED_TEXT']= nil 66 | ENV['TM_CURRENT_LINE'] = <<-EOF 67 | BeforeAfter 68 | EOF 69 | ENV['TM_LINE_INDEX'] = '10' 70 | ENV['TM_TAB_SIZE'] = '2' 71 | assert_equal 'BeforeAfter', Word.current_word 72 | assert_equal 'Before', Word.current_word('a-zA-Z0-9',:left) 73 | assert_equal 'After', Word.current_word('a-zA-Z0-9',:right) 74 | 75 | assert_equal ' Before', Word.current_word(" a-zA-Z",:left) 76 | assert_equal 'After ', Word.current_word(" a-zA-Z",:right) 77 | end 78 | 79 | def test_with_tabs 80 | ENV['TM_SELECTED_TEXT']= nil 81 | ENV['TM_CURRENT_LINE'] = <<-EOF 82 | BeforeAfter 83 | EOF 84 | ENV['TM_LINE_INDEX'] = '8' 85 | ENV['TM_TAB_SIZE'] = '2' 86 | assert_equal 'BeforeAfter', Word.current_word 87 | assert_equal 'Before', Word.current_word('a-zA-Z0-9',:left) 88 | assert_equal 'After', Word.current_word('a-zA-Z0-9',:right) 89 | 90 | assert_equal "\t\tBefore", Word.current_word("\ta-zA-Z",:left) 91 | assert_equal "After\t\t", Word.current_word("\ta-zA-Z",:right) 92 | end 93 | 94 | def test_with_dash 95 | ENV['TM_SELECTED_TEXT']= nil 96 | ENV['TM_CURRENT_LINE'] = <<-EOF 97 | Before--After 98 | EOF 99 | ENV['TM_LINE_INDEX'] = '11' 100 | ENV['TM_TAB_SIZE'] = '2' 101 | assert_equal 'Before--After', Word.current_word('-a-zA-Z0-9') 102 | assert_equal 'Before-', Word.current_word('-a-zA-Z0-9',:left) 103 | assert_equal '-After', Word.current_word('-a-zA-Z0-9',:right) 104 | 105 | assert_equal 'Before-', Word.current_word("\ta-zA-Z\-",:left) 106 | end 107 | 108 | def test_hash_result 109 | ENV['TM_SELECTED_TEXT']= nil 110 | ENV['TM_CURRENT_LINE'] = <<-EOF 111 | before_match BeforeAfter after_match 112 | EOF 113 | ENV['TM_LINE_INDEX'] = '22' 114 | ENV['TM_TAB_SIZE'] = '2' 115 | 116 | word = Word.current_word("a-zA-Z",:hash) 117 | 118 | assert_equal ENV['TM_CURRENT_LINE'], "#{word[:line]}" 119 | assert_equal 'Before', word[:left] 120 | assert_equal 'After', word[:right] 121 | end 122 | =begin 123 | =end 124 | def test_both_result 125 | ENV['TM_SELECTED_TEXT']= nil 126 | ENV['TM_CURRENT_LINE'] = <<-EOF 127 | before_match BeforeAfter after_match 128 | EOF 129 | ENV['TM_LINE_INDEX'] = '22' 130 | ENV['TM_TAB_SIZE'] = '2' 131 | 132 | assert_equal 'BeforeAfter', Word.current_word("a-zA-Z",:both) 133 | end 134 | 135 | def test_should_support_custom_regex 136 | ENV['TM_SELECTED_TEXT']= nil 137 | ENV['TM_CURRENT_LINE'] = <<-EOF 138 | before_match BeforeAfter after_match 139 | EOF 140 | ENV['TM_LINE_INDEX'] = '22' 141 | ENV['TM_TAB_SIZE'] = '2' 142 | 143 | assert_equal '', Word.current_word(/[a-zA-Z]/,:both) # No match since no capture group was used! 144 | 145 | assert_equal 'ef', Word.current_word(/([a-z])/,:both) # Capture group, but only selecting a single caracter before and after the caret 146 | assert_equal 'eforefter', Word.current_word(/([a-z]+)/,:both) # Only lowercase characters 147 | assert_equal 'BeforeAfter', Word.current_word(/^([a-z]*)/i,:both) # Ignore case 148 | assert_equal 'BeforeAfter', Word.current_word(/^([a-zA-Z]*)/,:both) # Explicit case 149 | end 150 | 151 | def test_should_support_custom_regex_example1 152 | ENV['TM_SELECTED_TEXT']= nil 153 | ENV['TM_CURRENT_LINE'] = <<-EOF 154 | background-color: ; 155 | EOF 156 | ENV['TM_LINE_INDEX'] = '20' 157 | ENV['TM_TAB_SIZE'] = '2' 158 | 159 | assert_equal ' background-color: ', Word.current_word(/^(.*)/,:left) 160 | assert_equal 'background-color', Word.current_word(/:([-a-z]+)/,:left) 161 | end 162 | 163 | def test_should_support_custom_regex_example2 164 | ENV['TM_SELECTED_TEXT']= nil 165 | ENV['TM_CURRENT_LINE'] = <<-EOF 166 |

Lorem ipsum dolor sit amet

167 | EOF 168 | ENV['TM_LINE_INDEX'] = '44' 169 | ENV['TM_TAB_SIZE'] = '2' 170 | 171 | assert_equal %Q{\t

Lorem ipsum}, Word.current_word(/^(.*)/,:left) 172 | assert_equal 'Lorem ipsum dolor sit amet', Word.current_word(/^([^<>]+)/i,:both) 173 | 174 | # You have to reverse the regex since it's matching against the reverse of the text before the caret 175 | assert_equal 'p', Word.current_word(/([-:a-z]+))?/,'

'); 88 | 89 | tip += '

'; 90 | tip += ''; 91 | tip += this.display.replace(/\t/g,'').replace(/\:|$/,':'); 92 | tip += "
(\n"; 93 | 94 | var objectKey; 95 | var objectProperties; 96 | 97 | if (this.params && this.params.length) 98 | for (var i = -1, param; param = this.params[++i];){ 99 | 100 | if (param.description) if (objectKey = param.description.match(/properties defined in .*?>([^<]*)' 119 | 120 | // sys.print(tip.replace(/" 128 | + "" 129 | + p.name 130 | + "" 131 | + "" 132 | + ":" 133 | + p.type 134 | + "" 135 | + "" 136 | + "\n\t\t" 137 | + "" 138 | + (p.description || p.value).replace(/<[^>]*>/,'') 139 | + "" 140 | + "\n" 141 | ; 142 | return html; 143 | } 144 | 145 | 146 | Suggestion.Method.prototype.updateInsertSnippet = function(){ 147 | snip = new Snippet(''); 148 | var add; 149 | if (this.params && this.params.length){ 150 | for (var i = -1, param; param = this.params[++i];){ 151 | if (i == 0) snip.appendText("(").begin(); 152 | else snip.appendText(", "); 153 | 154 | var isLast = !this.params[i+1]; 155 | add = (isLast ? 'addLast' : 'addPlaceholder'); 156 | 157 | switch (param.type){ 158 | case 'string': snip.appendText("'")[add](param.name).appendText("'"); break; 159 | case 'object': snip.appendText("{")[add]().appendText("}"); break; 160 | case 'function': snip.appendText("function ").addPlaceholder(param.name).appendText("(").addPlaceholder().appendText(")").appendText("{")[add]().appendText("}"); break; 161 | default: 162 | snip.addPlaceholder(param.name); 163 | } 164 | } 165 | snip.end().appendText(')'); 166 | } 167 | else snip.appendRaw('($0)'); 168 | 169 | // sys.p(snip) 170 | 171 | this.insert = snip; 172 | }; 173 | Suggestion.Method.prototype.addParams = function(params){ 174 | if (!(params && params.length)) return this; 175 | for (var i = -1, param; param = params[++i];){ 176 | this.addParam(param, false); 177 | } 178 | this.updateToolTip(); 179 | this.updateInsertSnippet(); 180 | return this; 181 | }; 182 | Suggestion.Method.prototype.addParam = function(param, shouldUpdate){ 183 | this.params.push(param); 184 | if (shouldUpdate !== false) this.updateToolTip().updateInsertSnippet(); 185 | return this; 186 | }; 187 | 188 | 189 | Suggestion.Property = function(options){ 190 | if (!(this instanceof Suggestion.Property)) return new Suggestion.Property(options); 191 | Suggestion.call(this, options); 192 | }; 193 | Suggestion.Property.prototype = Object.create(Suggestion.prototype); 194 | Suggestion.Property.prototype.image = "Property"; 195 | 196 | Suggestion.fromAPI = function(api, namespace, API){ 197 | 198 | Suggestion.completions.suggestions.push( 199 | new Suggestion({ 200 | API:API, 201 | tool_tip : api.description +'


'+ (api.notes || ''), 202 | match : namespace, 203 | display : 'NS\t' + ''.rjust('string,float '.length,'_') + '\t' + namespace, 204 | image : "Namespace" 205 | }) 206 | .toJSON() 207 | ); 208 | 209 | if (api.methods && api.methods.length) 210 | api.methods.forEach(function(method){ 211 | // sys.p(method.name, method.returntype, method.parameters.map(function(param){ return param.name+':'+param.type })); 212 | Suggestion.completions.suggestions.push( 213 | new Suggestion.Method({ 214 | API:API, 215 | match : namespace +'.'+ method.name, 216 | display : 'M\t' + method.returntype.rjust('string,float '.length,'_') + '\t' + namespace +'.'+ method.name + '()', 217 | description: method.value 218 | }) 219 | .addParams(method.parameters) 220 | .toJSON() 221 | ); 222 | }); 223 | 224 | if (api.properties && api.properties.length) 225 | api.properties.forEach(function(property){ 226 | if (!property.name) return; 227 | 228 | Suggestion.completions.suggestions.push( 229 | new Suggestion.Property({ 230 | API:API, 231 | tool_tip : property.value, 232 | 233 | match : namespace +'.'+ property.name, 234 | display : 'P\t' + property.type.rjust('string,float '.length,'_') + '\t' + namespace.replace('Titanium','') +'.'+ property.name 235 | }) 236 | .toJSON() 237 | ); 238 | }); 239 | 240 | }; 241 | 242 | if (typeof exports == 'undefined') var exports = {}; 243 | exports.Suggestion = Suggestion; 244 | 245 | -------------------------------------------------------------------------------- /Support/lib/tm/complete.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | =begin 3 | --- 4 | url : http://gist.github.com/437012 5 | name : complete.rb 6 | description : "Ruby Code Completion framework for TextMate. Supports ENV var, JSON & Plist completion lists & options." 7 | 8 | authors : Thomas Aylott 9 | copyright : © 2008-2010 Thomas Aylott 10 | license : MIT 11 | 12 | provides : 13 | - "TextMate::Complete" 14 | - "TextMate::Complete#complete!" 15 | - "TextMate::Complete#tip!" 16 | 17 | requires : 18 | - "TextMate::UI.complete" 19 | - "TextMate::UI.tool_tip" 20 | - "Word.current_word" 21 | - "OSX::PropertyList" 22 | - "JSON.parse" 23 | ... 24 | =end 25 | require File.dirname(__FILE__) + "/../current_word" 26 | require ENV['TM_SUPPORT_PATH'] + '/lib/ui' 27 | require ENV['TM_SUPPORT_PATH'] + '/lib/escape' 28 | require ENV['TM_SUPPORT_PATH'] + '/lib/osx/plist' 29 | require 'rubygems' 30 | require 'json' 31 | require 'shellwords' 32 | 33 | module TextMate 34 | class Complete 35 | IMAGES_FOLDER_NAME = 'icons' 36 | IMAGES = { 37 | "C" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Commands.png", 38 | "D" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Drag Commands.png", 39 | "L" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Languages.png", 40 | "M" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Macros.png", 41 | "P" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Preferences.png", 42 | "S" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Snippets.png", 43 | "T" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Templates.png", 44 | "Doc" => "/Applications/TextMate.app/Contents/Resources/Bundle Item Icons/Template Files.png", 45 | } 46 | 47 | def initialize 48 | end 49 | 50 | # 0-config completion command using environment variables for everything 51 | def complete! 52 | return choices unless choices 53 | TextMate::UI.complete(choices, {:images => images, :extra_chars => extra_chars}) do |result| 54 | TextMate::UI.tool_tip( result['tool_tip'], {:format => result['tool_tip_format'] || :text }) 55 | result ? result['insert'] : nil 56 | end 57 | end 58 | 59 | def tip! 60 | # If there is no current_word, then check the next current_word 61 | # If there really is no current_word, then show a menu of choices 62 | 63 | chars = "a-zA-Z0-9" # Hard-coded into D2 64 | chars += Regexp.escape(extra_chars) if extra_chars 65 | current_word ||= Word.current_word chars, :both 66 | 67 | result = nil 68 | menu_choices = nil 69 | choices = nil 70 | choice = 0 71 | 72 | [current_word, current_method_name, current_collection_name].each do |initial_filter| 73 | next unless initial_filter and not initial_filter.empty? 74 | # p initial_filter 75 | 76 | choices = self.choices.select { |c| (c['match'] || c['display']) =~ /^#{Regexp.quote(initial_filter)}/ } 77 | 78 | # p choices 79 | break if choices and not choices.empty? 80 | end 81 | choices ||= self.choices 82 | 83 | menu_choices = choices.map { |c| c['display'] } 84 | choice = TextMate::UI.menu(menu_choices) if menu_choices and menu_choices.length > 1 85 | if choice 86 | result = choices[choice] 87 | end 88 | result = {'tool_tip' => 'No information'} unless result 89 | 90 | TextMate::UI.tool_tip( result['tool_tip'], {:format => result['tool_tip_format'] || :text }) 91 | end 92 | 93 | def choices 94 | @choices ||= data['suggestions'] 95 | end 96 | def choices= choice_array 97 | @choices = array_to_suggestions(choice_array) 98 | end 99 | 100 | def images 101 | @images = data['images'] || IMAGES 102 | 103 | data['images']||{}.each_pair do |name,path| 104 | @images[name] = path 105 | next if File.exists? @images[name] 106 | @images[name] = ENV['TM_BUNDLE_SUPPORT'] + "/#{IMAGES_FOLDER_NAME}/" + path 107 | next if File.exists? @images[name] 108 | @images[name] = ENV['TM_SUPPORT_PATH'] + "/#{IMAGES_FOLDER_NAME}/" + path 109 | next if File.exists? @images[name] 110 | end 111 | 112 | @images 113 | end 114 | 115 | def tool_tip_prefix 116 | @tool_tip_prefix ||= data['tool_tip_prefix'] 117 | end 118 | 119 | def extra_chars 120 | ENV['TM_COMPLETIONS_EXTRACHARS'] || data['extra_chars'] 121 | end 122 | 123 | def chars 124 | "a-zA-Z0-9" 125 | end 126 | 127 | private 128 | def data(raw_data=nil) 129 | fix_legacy 130 | 131 | raw_data ||= read_data 132 | return {} unless raw_data and not raw_data.empty? 133 | 134 | @data = parse_data raw_data 135 | return @data 136 | end 137 | 138 | def read_data 139 | raw_data = read_file 140 | raw_data ||= read_string 141 | raw_data 142 | end 143 | 144 | def read_file 145 | paths = [ENV['TM_COMPLETIONS_FILE']] if ENV['TM_COMPLETIONS_FILE'] 146 | paths ||= Shellwords.shellwords( ENV['TM_COMPLETIONS_FILES'] ) if ENV.has_key?('TM_COMPLETIONS_FILES') 147 | return nil unless paths 148 | 149 | paths.map do |path| 150 | next unless path and not path.empty? 151 | path = ENV['TM_BUNDLE_SUPPORT'] + '/' + path unless File.exists? path 152 | next unless File.exists? path 153 | 154 | { :data => File.read(path), 155 | :format => path.scan(/\.([^\.]+)$/).last.last 156 | } 157 | end 158 | end 159 | 160 | def read_string 161 | [{:data => ENV['TM_COMPLETIONS'], 162 | :format => ENV['TM_COMPLETIONS_SPLIT'] 163 | }] 164 | end 165 | 166 | attr_accessor :filepath 167 | 168 | def parse_data(raw_datas) 169 | return @parsed if @parsed 170 | parsed = {"suggestions"=>[]} 171 | 172 | raw_datas.each do |raw_data| 173 | suggestions = parsed['suggestions'] 174 | 175 | case raw_data[:format] 176 | when 'plist' 177 | par = parse_plist(raw_data) 178 | when 'json' 179 | par = parse_json(raw_data) 180 | when "txt" 181 | raw_data[:format] = "\n" 182 | par = parse_string(raw_data) 183 | when nil 184 | raw_data[:format] = "," 185 | par = parse_string(raw_data) 186 | else 187 | par = parse_string(raw_data) 188 | end 189 | 190 | if par['tool_tip_prefix'] 191 | par['suggestions'] = par['suggestions'].map do |suggestion| 192 | suggestion['tool_tip'] = par['tool_tip_prefix'] + suggestion['tool_tip'] 193 | suggestion 194 | end 195 | end 196 | 197 | parsed.merge! par 198 | parsed['suggestions'] = suggestions + parsed['suggestions'] 199 | end 200 | 201 | @parsed = parsed 202 | end 203 | def parse_string(raw_data) 204 | return {} unless raw_data and raw_data[:data] 205 | return raw_data[:data] unless raw_data[:data].respond_to? :to_str 206 | raw_data[:data] = raw_data[:data].to_str 207 | 208 | data = {} 209 | data['suggestions'] = array_to_suggestions(raw_data[:data].split(raw_data[:format])) 210 | data 211 | end 212 | def parse_plist(raw_data) 213 | OSX::PropertyList.load(raw_data[:data]) 214 | end 215 | def parse_json(raw_data) 216 | JSON.parse(raw_data[:data]) 217 | end 218 | 219 | def array_to_suggestions(suggestions) 220 | suggestions.delete('') 221 | 222 | suggestions.map! do |c| 223 | {'display' => c} 224 | end 225 | 226 | suggestions 227 | end 228 | def current_method_name 229 | # Regex for finding a method or function name that's close to your caret position using Word.current_word 230 | # TODO: Allow completion prefs to define their own Complete.tip! method_name 231 | 232 | characters = "a-zA-Z0-9" # Hard-coded into D2 233 | characters += Regexp.escape(extra_chars) if extra_chars 234 | 235 | regex = %r/ 236 | (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]+ | \) (?> [^\(\)]* ) \( | )+ \( | )+ \( | )+ \( | )+ \( | )+ 237 | (?: \(([#{characters}]+) )?/ix 238 | 239 | Word.current_word(regex,:left) 240 | end 241 | def current_collection_name 242 | characters = "a-zA-Z0-9" # Hard-coded into D2 243 | characters += Regexp.escape(extra_chars) if extra_chars 244 | 245 | regex = %r/ 246 | (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]+ | \] (?> [^\[\]]* ) \[ | )+ \[ | )+ \[ | )+ \[ | )+ \[ | )+ 247 | (?: \[([#{characters}]+) )?/ix 248 | 249 | Word.current_word(regex,:left) 250 | end 251 | 252 | def fix_legacy 253 | ENV['TM_COMPLETIONS_SPLIT'] ||= ENV['TM_COMPLETIONS_split'] 254 | end 255 | end 256 | end 257 | 258 | if __FILE__ == $0 259 | 260 | `open "txmt://open?url=file://$TM_FILEPATH"` #For testing purposes, make this document the topmost so that the complete popup works 261 | ENV['WEB_PREVIEW_RUBY']='NO-RUN' 262 | require "test/unit" 263 | # require "complete" 264 | 265 | class TestComplete < Test::Unit::TestCase 266 | def setup 267 | @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,' 268 | 269 | @plist_raw = <<-'PLIST' 270 | { suggestions = ( 271 | { 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."; }, 272 | { 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."; }, 273 | { 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."; } 274 | ); 275 | extra_chars = '.'; 276 | images = { 277 | Command = "Commands.png"; 278 | Drag = "Drag Commands.png"; 279 | Language = "Languages.png"; 280 | Macro = "Macros.png"; 281 | Preference = "Preferences.png"; 282 | Snippet = "Snippets.png"; 283 | Template = "Template Files.png"; 284 | Templates = "Templates.png"; 285 | }; 286 | } 287 | PLIST 288 | 289 | @json_raw = <<-'JSON' 290 | { 291 | "extra_chars": "-_$.", 292 | "suggestions": [ 293 | { "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." }, 294 | { "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." }, 295 | { "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." } 296 | ], 297 | "images": { 298 | "String" : "String.png", 299 | "RegExp" : "RegExp.png", 300 | "Number" : "Number.png", 301 | "Array" : "Array.png", 302 | "Function": "Function.png", 303 | "Object" : "Object.png", 304 | "Node" : "Node.png", 305 | "NodeList": "NodeList.png" 306 | } 307 | } 308 | JSON 309 | end 310 | 311 | def test_basic_complete 312 | ENV['TM_COMPLETIONS'] = @string_raw 313 | 314 | assert_equal ENV['TM_COMPLETIONS'].split(','), TextMate::Complete.new.choices.map{|c| c['display']} 315 | assert_equal TextMate::Complete::IMAGES, TextMate::Complete.new.images 316 | 317 | TextMate::Complete.new.complete! 318 | end 319 | # 320 | def test_should_support_plist 321 | ENV['TM_COMPLETIONS_SPLIT']='plist' 322 | ENV['TM_COMPLETIONS'] = @plist_raw 323 | TextMate::Complete.new.complete! 324 | end 325 | # 326 | def test_should_support_json 327 | ENV.delete 'TM_COMPLETIONS' 328 | assert_nil(ENV['TM_COMPLETIONS']) 329 | ENV.delete 'TM_COMPLETIONS_SPLIT' 330 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 331 | 332 | ENV['TM_COMPLETIONS_SPLIT']='json' 333 | ENV['TM_COMPLETIONS'] = @json_raw 334 | fred = TextMate::Complete.new 335 | assert_equal(3, fred.choices.length) 336 | end 337 | # 338 | def test_should_be_able_to_modify_the_choices 339 | ENV['TM_COMPLETIONS'] = @string_raw 340 | 341 | fred = TextMate::Complete.new 342 | 343 | assert_not_nil fred.choices 344 | assert_equal ENV['TM_COMPLETIONS'].split(','), fred.choices.map{|c| c['display']} 345 | fred.choices.reject!{|choice| choice['display'] !~ /^a/ } 346 | assert_equal ENV['TM_COMPLETIONS'].split(',').grep(/^a/), fred.choices.map{|c| c['display']} 347 | 348 | fred.choices=%w[fred is not my name] 349 | assert_equal %w[fred is not my name], fred.choices.map{|c| c['display']} 350 | end 351 | # 352 | def test_should_parse_files_based_on_extension_plist 353 | ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.plist' 354 | 355 | File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @plist_raw } 356 | assert File.exists?(ENV['TM_COMPLETIONS_FILE']) 357 | 358 | fred = TextMate::Complete.new 359 | assert_equal(['moo', 'foo', 'bar'], fred.choices.map{|c| c['display']}) 360 | end 361 | # 362 | def test_should_parse_files_based_on_extension_txt 363 | ENV.delete 'TM_COMPLETIONS' 364 | assert_nil(ENV['TM_COMPLETIONS']) 365 | ENV.delete 'TM_COMPLETIONS_SPLIT' 366 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 367 | 368 | ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.txt' 369 | 370 | File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @string_raw.gsub(',',"\n") } 371 | assert File.exists?(ENV['TM_COMPLETIONS_FILE']) 372 | 373 | fred = TextMate::Complete.new 374 | 375 | assert_equal(@string_raw.split(','), fred.choices.map{|c| c['display']}) 376 | end 377 | # 378 | def test_should_parse_multiple_files 379 | ENV.delete 'TM_COMPLETIONS' 380 | assert_nil(ENV['TM_COMPLETIONS']) 381 | ENV.delete 'TM_COMPLETIONS_SPLIT' 382 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 383 | ENV.delete 'TM_COMPLETIONS_FILE' 384 | assert_nil(ENV['TM_COMPLETIONS_FILE']) 385 | 386 | ENV['TM_COMPLETIONS_FILES'] = "'/tmp/completions_test.txt' '/tmp/completions_test1.txt' '/tmp/completions_test2.txt'" 387 | 388 | require 'shellwords' 389 | Shellwords.shellwords( ENV['TM_COMPLETIONS_FILES'] ).each_with_index do |filepath,i| 390 | File.open(filepath,'w'){|file| file.write @string_raw.gsub(',',"#{i}\n") } 391 | assert File.exists?(filepath) 392 | end 393 | 394 | fred = TextMate::Complete.new 395 | 396 | assert_equal(@string_raw.split(',').uniq.length * 3, fred.choices.map{|c| c['display']}.length ) 397 | end 398 | # 399 | def test_should_override_split_with_extension 400 | ENV['TM_COMPLETIONS_SPLIT'] = ',' 401 | ENV['TM_COMPLETIONS_FILE'] = '/tmp/completions_test.plist' 402 | 403 | File.open(ENV['TM_COMPLETIONS_FILE'],'w'){|file| file.write @plist_raw } 404 | assert File.exists?(ENV['TM_COMPLETIONS_FILE']) 405 | 406 | fred = TextMate::Complete.new 407 | assert_equal(['moo', 'foo', 'bar'], fred.choices.map{|c| c['display']}) 408 | end 409 | # 410 | def test_should_get_extra_chars_from_var 411 | ENV['TM_COMPLETIONS_SPLIT']=',' 412 | ENV['TM_COMPLETIONS'] = @string_raw 413 | ENV['TM_COMPLETIONS_EXTRACHARS'] = '.' 414 | 415 | fred = TextMate::Complete.new 416 | assert_equal('.', fred.extra_chars) 417 | end 418 | # 419 | def test_should_get_extra_chars_from_plist 420 | ENV['TM_COMPLETIONS_SPLIT']='plist' 421 | ENV['TM_COMPLETIONS'] = @plist_raw 422 | 423 | assert_nil(ENV['TM_COMPLETIONS_EXTRACHARS']) 424 | 425 | fred = TextMate::Complete.new 426 | assert_equal('.', fred.extra_chars) 427 | end 428 | # TODO: should_fix_image_paths 429 | # =begin 430 | def test_should_fix_image_paths 431 | ENV['TM_COMPLETIONS_SPLIT'] = 'plist' 432 | ENV['TM_COMPLETIONS'] = @plist_raw 433 | ENV['TM_BUNDLE_SUPPORT'] = '/tmp' 434 | ENV['TM_SUPPORT_PATH'] = '/tmp' 435 | 436 | images = OSX::PropertyList.load(@plist_raw)['images'] 437 | 438 | FileUtils.mkdir_p "#{ENV['TM_SUPPORT_PATH']}/#{TextMate::Complete::IMAGES_FOLDER_NAME}" 439 | images.each_pair do |name,path| 440 | File.open("#{ENV['TM_SUPPORT_PATH']}/#{TextMate::Complete::IMAGES_FOLDER_NAME}/#{path}", 'w'){ |file| file.write('') } 441 | end 442 | 443 | TextMate::Complete.new.images.each_pair do |name,path| 444 | p path 445 | assert File.exists?(path) 446 | end 447 | 448 | end 449 | =begin 450 | 451 | =end 452 | def test_should_apply_prefix 453 | ENV.delete 'TM_COMPLETIONS' 454 | assert_nil(ENV['TM_COMPLETIONS']) 455 | ENV.delete 'TM_COMPLETIONS_SPLIT' 456 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 457 | 458 | @json_raw = <<-'JSON' 459 | { 460 | "extra_chars": "-_$.", 461 | "tool_tip_prefix":"prefix", 462 | "suggestions": [ 463 | { "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." }, 464 | { "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." }, 465 | { "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." } 466 | ], 467 | "images": { 468 | "String" : "String.png", 469 | "RegExp" : "RegExp.png", 470 | "Number" : "Number.png", 471 | "Array" : "Array.png", 472 | "Function": "Function.png", 473 | "Object" : "Object.png", 474 | "Node" : "Node.png", 475 | "NodeList": "NodeList.png" 476 | } 477 | } 478 | JSON 479 | 480 | ENV['TM_COMPLETIONS_SPLIT']='json' 481 | ENV['TM_COMPLETIONS'] = @json_raw 482 | fred = TextMate::Complete.new 483 | assert_equal(3, fred.choices.length) 484 | assert fred.choices.first['tool_tip'].match(/^prefix/) 485 | end 486 | # 487 | def test_should_show_tooltip_without_inserting_anything 488 | # This method passes if it shows a tooltip when selecting a menu-item 489 | # and DOESN'T insert anything or cause the document think anything has changed 490 | ENV.delete 'TM_COMPLETIONS' 491 | assert_nil(ENV['TM_COMPLETIONS']) 492 | ENV.delete 'TM_COMPLETIONS_SPLIT' 493 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 494 | 495 | ENV['TM_COMPLETIONS_SPLIT']='json' 496 | ENV['TM_COMPLETIONS'] = @json_raw 497 | fred = TextMate::Complete.new 498 | assert_equal(3, fred.choices.length) 499 | 500 | TextMate::Complete.new.tip! 501 | end 502 | # 503 | def test_tip_should_look_for_the_current_word_and_then_try_the_closest_function_name 504 | ENV.delete 'TM_COMPLETIONS' 505 | assert_nil(ENV['TM_COMPLETIONS']) 506 | ENV.delete 'TM_COMPLETIONS_SPLIT' 507 | assert_nil(ENV['TM_COMPLETIONS_SPLIT']) 508 | 509 | ENV['TM_COMPLETIONS_SPLIT']='json' 510 | ENV['TM_COMPLETIONS'] = @json_raw 511 | fred = TextMate::Complete.new 512 | assert_equal(3, fred.choices.length) 513 | 514 | TextMate::Complete.new.tip! 515 | # 516 | # This test passes if, when run, you see the tooltip for closest function 517 | # Be sure to more your caret around and try a few times 518 | # Showing a menu is a fail 519 | # 520 | # foo( bar( ), '.moo' ) 521 | # ^ Caret here should give the tip for 'foo' 522 | # ^ Caret here should give the tip for 'bar' 523 | # ^ Caret here should give the tip for 'bar' 524 | # ^ Caret here should give the tip for '.moo' 525 | # foo( bar(one,two,foo), '.moo' ) 526 | # foo['bar'] 527 | end 528 | # 529 | end 530 | 531 | end#if 532 | -------------------------------------------------------------------------------- /Support/lib/global-es5.js: -------------------------------------------------------------------------------- 1 | 2 | // -- kriskowal Kris Kowal Copyright (C) 2009-2010 MIT License 3 | // -- tlrobinson Tom Robinson 4 | // dantman Daniel Friesen 5 | 6 | /*! 7 | Copyright (c) 2009, 280 North Inc. http://280north.com/ 8 | MIT License. http://github.com/280north/narwhal/blob/master/README.md 9 | */ 10 | 11 | // Brings an environment as close to ECMAScript 5 compliance 12 | // as is possible with the facilities of erstwhile engines. 13 | 14 | // ES5 Draft 15 | // http://www.ecma-international.org/publications/files/drafts/tc39-2009-050.pdf 16 | 17 | // NOTE: this is a draft, and as such, the URL is subject to change. If the 18 | // link is broken, check in the parent directory for the latest TC39 PDF. 19 | // http://www.ecma-international.org/publications/files/drafts/ 20 | 21 | // Previous ES5 Draft 22 | // http://www.ecma-international.org/publications/files/drafts/tc39-2009-025.pdf 23 | // This is a broken link to the previous draft of ES5 on which most of the 24 | // numbered specification references and quotes herein were taken. Updating 25 | // these references and quotes to reflect the new document would be a welcome 26 | // volunteer project. 27 | 28 | // 29 | // Array 30 | // ===== 31 | // 32 | 33 | // ES5 15.4.3.2 34 | if (!Array.isArray) { 35 | Array.isArray = function(obj) { 36 | return Object.prototype.toString.call(obj) == "[object Array]"; 37 | }; 38 | } 39 | 40 | // ES5 15.4.4.18 41 | if (!Array.prototype.forEach) { 42 | Array.prototype.forEach = function(block, thisObject) { 43 | var len = this.length >>> 0; 44 | for (var i = 0; i < len; i++) { 45 | if (i in this) { 46 | block.call(thisObject, this[i], i, this); 47 | } 48 | } 49 | }; 50 | } 51 | 52 | // ES5 15.4.4.19 53 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map 54 | if (!Array.prototype.map) { 55 | Array.prototype.map = function(fun /*, thisp*/) { 56 | var len = this.length >>> 0; 57 | if (typeof fun != "function") 58 | throw new TypeError(); 59 | 60 | var res = new Array(len); 61 | var thisp = arguments[1]; 62 | for (var i = 0; i < len; i++) { 63 | if (i in this) 64 | res[i] = fun.call(thisp, this[i], i, this); 65 | } 66 | 67 | return res; 68 | }; 69 | } 70 | 71 | // ES5 15.4.4.20 72 | if (!Array.prototype.filter) { 73 | Array.prototype.filter = function (block /*, thisp */) { 74 | var values = []; 75 | var thisp = arguments[1]; 76 | for (var i = 0; i < this.length; i++) 77 | if (block.call(thisp, this[i])) 78 | values.push(this[i]); 79 | return values; 80 | }; 81 | } 82 | 83 | // ES5 15.4.4.16 84 | if (!Array.prototype.every) { 85 | Array.prototype.every = function (block /*, thisp */) { 86 | var thisp = arguments[1]; 87 | for (var i = 0; i < this.length; i++) 88 | if (!block.call(thisp, this[i])) 89 | return false; 90 | return true; 91 | }; 92 | } 93 | 94 | // ES5 15.4.4.17 95 | if (!Array.prototype.some) { 96 | Array.prototype.some = function (block /*, thisp */) { 97 | var thisp = arguments[1]; 98 | for (var i = 0; i < this.length; i++) 99 | if (block.call(thisp, this[i])) 100 | return true; 101 | return false; 102 | }; 103 | } 104 | 105 | // ES5 15.4.4.21 106 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce 107 | if (!Array.prototype.reduce) { 108 | Array.prototype.reduce = function(fun /*, initial*/) { 109 | var len = this.length >>> 0; 110 | if (typeof fun != "function") 111 | throw new TypeError(); 112 | 113 | // no value to return if no initial value and an empty array 114 | if (len == 0 && arguments.length == 1) 115 | throw new TypeError(); 116 | 117 | var i = 0; 118 | if (arguments.length >= 2) { 119 | var rv = arguments[1]; 120 | } else { 121 | do { 122 | if (i in this) { 123 | rv = this[i++]; 124 | break; 125 | } 126 | 127 | // if array contains no values, no initial value to return 128 | if (++i >= len) 129 | throw new TypeError(); 130 | } while (true); 131 | } 132 | 133 | for (; i < len; i++) { 134 | if (i in this) 135 | rv = fun.call(null, rv, this[i], i, this); 136 | } 137 | 138 | return rv; 139 | }; 140 | } 141 | 142 | // ES5 15.4.4.22 143 | // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight 144 | if (!Array.prototype.reduceRight) { 145 | Array.prototype.reduceRight = function(fun /*, initial*/) { 146 | var len = this.length >>> 0; 147 | if (typeof fun != "function") 148 | throw new TypeError(); 149 | 150 | // no value to return if no initial value, empty array 151 | if (len == 0 && arguments.length == 1) 152 | throw new TypeError(); 153 | 154 | var i = len - 1; 155 | if (arguments.length >= 2) { 156 | var rv = arguments[1]; 157 | } else { 158 | do { 159 | if (i in this) { 160 | rv = this[i--]; 161 | break; 162 | } 163 | 164 | // if array contains no values, no initial value to return 165 | if (--i < 0) 166 | throw new TypeError(); 167 | } while (true); 168 | } 169 | 170 | for (; i >= 0; i--) { 171 | if (i in this) 172 | rv = fun.call(null, rv, this[i], i, this); 173 | } 174 | 175 | return rv; 176 | }; 177 | } 178 | 179 | // ES5 15.4.4.14 180 | if (!Array.prototype.indexOf) { 181 | Array.prototype.indexOf = function (value /*, fromIndex */ ) { 182 | var length = this.length; 183 | if (!length) 184 | return -1; 185 | var i = arguments[1] || 0; 186 | if (i >= length) 187 | return -1; 188 | if (i < 0) 189 | i += length; 190 | for (; i < length; i++) { 191 | if (!Object.prototype.hasOwnProperty.call(this, i)) 192 | continue; 193 | if (value === this[i]) 194 | return i; 195 | } 196 | return -1; 197 | }; 198 | } 199 | 200 | // ES5 15.4.4.15 201 | if (!Array.prototype.lastIndexOf) { 202 | Array.prototype.lastIndexOf = function (value /*, fromIndex */) { 203 | var length = this.length; 204 | if (!length) 205 | return -1; 206 | var i = arguments[1] || length; 207 | if (i < 0) 208 | i += length; 209 | i = Math.min(i, length - 1); 210 | for (; i >= 0; i--) { 211 | if (!Object.prototype.hasOwnProperty.call(this, i)) 212 | continue; 213 | if (value === this[i]) 214 | return i; 215 | } 216 | return -1; 217 | }; 218 | } 219 | 220 | // 221 | // Object 222 | // ====== 223 | // 224 | 225 | // ES5 15.2.3.2 226 | if (!Object.getPrototypeOf) { 227 | Object.getPrototypeOf = function (object) { 228 | return object.__proto__; 229 | // or undefined if not available in this engine 230 | }; 231 | } 232 | 233 | // ES5 15.2.3.3 234 | if (!Object.getOwnPropertyDescriptor) { 235 | Object.getOwnPropertyDescriptor = function (object) { 236 | return {}; // XXX 237 | }; 238 | } 239 | 240 | // ES5 15.2.3.4 241 | if (!Object.getOwnPropertyNames) { 242 | Object.getOwnPropertyNames = function (object) { 243 | return Object.keys(object); 244 | }; 245 | } 246 | 247 | // ES5 15.2.3.5 248 | if (!Object.create) { 249 | Object.create = function(prototype, properties) { 250 | if (typeof prototype != "object" || prototype === null) 251 | throw new TypeError("typeof prototype["+(typeof prototype)+"] != 'object'"); 252 | function Type() {}; 253 | Type.prototype = prototype; 254 | var object = new Type(); 255 | if (typeof properties !== "undefined") 256 | Object.defineProperties(object, properties); 257 | return object; 258 | }; 259 | } 260 | 261 | // ES5 15.2.3.6 262 | if (!Object.defineProperty) { 263 | Object.defineProperty = function(object, property, descriptor) { 264 | var has = Object.prototype.hasOwnProperty; 265 | if (typeof descriptor == "object" && object.__defineGetter__) { 266 | if (has.call(descriptor, "value")) { 267 | if (!object.__lookupGetter__(property) && !object.__lookupSetter__(property)) 268 | // data property defined and no pre-existing accessors 269 | object[property] = descriptor.value; 270 | if (has.call(descriptor, "get") || has.call(descriptor, "set")) 271 | // descriptor has a value property but accessor already exists 272 | throw new TypeError("Object doesn't support this action"); 273 | } 274 | // fail silently if "writable", "enumerable", or "configurable" 275 | // are requested but not supported 276 | /* 277 | // alternate approach: 278 | if ( // can't implement these features; allow false but not true 279 | !(has.call(descriptor, "writable") ? descriptor.writable : true) || 280 | !(has.call(descriptor, "enumerable") ? descriptor.enumerable : true) || 281 | !(has.call(descriptor, "configurable") ? descriptor.configurable : true) 282 | ) 283 | throw new RangeError( 284 | "This implementation of Object.defineProperty does not " + 285 | "support configurable, enumerable, or writable." 286 | ); 287 | */ 288 | else if (typeof descriptor.get == "function") 289 | object.__defineGetter__(property, descriptor.get); 290 | if (typeof descriptor.set == "function") 291 | object.__defineSetter__(property, descriptor.set); 292 | } 293 | return object; 294 | }; 295 | } 296 | 297 | // ES5 15.2.3.7 298 | if (!Object.defineProperties) { 299 | Object.defineProperties = function(object, properties) { 300 | for (var property in properties) { 301 | if (Object.prototype.hasOwnProperty.call(properties, property)) 302 | Object.defineProperty(object, property, properties[property]); 303 | } 304 | return object; 305 | }; 306 | } 307 | 308 | // ES5 15.2.3.8 309 | if (!Object.seal) { 310 | Object.seal = function (object) { 311 | return object; 312 | }; 313 | } 314 | 315 | // ES5 15.2.3.9 316 | if (!Object.freeze) { 317 | Object.freeze = function (object) { 318 | return object; 319 | }; 320 | // } else if (require("system").engine.indexOf("rhino") >= 0) { 321 | // // XXX workaround for a Rhino bug. 322 | // var freeze = Object.freeze; 323 | // Object.freeze = function (object) { 324 | // if (typeof object == "function") { 325 | // return object; 326 | // } else { 327 | // return freeze(object); 328 | // } 329 | // }; 330 | } 331 | 332 | // ES5 15.2.3.10 333 | if (!Object.preventExtensions) { 334 | Object.preventExtensions = function (object) { 335 | return object; 336 | }; 337 | } 338 | 339 | // ES5 15.2.3.11 340 | if (!Object.isSealed) { 341 | Object.isSealed = function (object) { 342 | return false; 343 | }; 344 | } 345 | 346 | // ES5 15.2.3.12 347 | if (!Object.isFrozen) { 348 | Object.isFrozen = function (object) { 349 | return false; 350 | }; 351 | } 352 | 353 | // ES5 15.2.3.13 354 | if (!Object.isExtensible) { 355 | Object.isExtensible = function (object) { 356 | return true; 357 | }; 358 | } 359 | 360 | // ES5 15.2.3.14 361 | if (!Object.keys) { 362 | Object.keys = function (object) { 363 | var keys = []; 364 | for (var name in object) { 365 | if (Object.prototype.hasOwnProperty.call(object, name)) { 366 | keys.push(name); 367 | } 368 | } 369 | return keys; 370 | }; 371 | } 372 | 373 | // 374 | // Date 375 | // ==== 376 | // 377 | 378 | // ES5 15.9.5.43 379 | // Format a Date object as a string according to a subset of the ISO-8601 standard. 380 | // Useful in Atom, among other things. 381 | if (!Date.prototype.toISOString) { 382 | Date.prototype.toISOString = function() { 383 | return ( 384 | this.getFullYear() + "-" + 385 | (this.getMonth() + 1) + "-" + 386 | this.getDate() + "T" + 387 | this.getHours() + ":" + 388 | this.getMinutes() + ":" + 389 | this.getSeconds() + "Z" 390 | ); 391 | } 392 | } 393 | 394 | // ES5 15.9.4.4 395 | if (!Date.now) { 396 | Date.now = function () { 397 | return new Date().getTime(); 398 | }; 399 | } 400 | 401 | // ES5 15.9.5.44 402 | if (!Date.prototype.toJSON) { 403 | Date.prototype.toJSON = function (key) { 404 | // This function provides a String representation of a Date object for 405 | // use by JSON.stringify (15.12.3). When the toJSON method is called 406 | // with argument key, the following steps are taken: 407 | 408 | // 1. Let O be the result of calling ToObject, giving it the this 409 | // value as its argument. 410 | // 2. Let tv be ToPrimitive(O, hint Number). 411 | // 3. If tv is a Number and is not finite, return null. 412 | // XXX 413 | // 4. Let toISO be the result of calling the [[Get]] internal method of 414 | // O with argument "toISOString". 415 | // 5. If IsCallable(toISO) is false, throw a TypeError exception. 416 | if (typeof this.toISOString != "function") 417 | throw new TypeError(); 418 | // 6. Return the result of calling the [[Call]] internal method of 419 | // toISO with O as the this value and an empty argument list. 420 | return this.toISOString(); 421 | 422 | // NOTE 1 The argument is ignored. 423 | 424 | // NOTE 2 The toJSON function is intentionally generic; it does not 425 | // require that its this value be a Date object. Therefore, it can be 426 | // transferred to other kinds of objects for use as a method. However, 427 | // it does require that any such object have a toISOString method. An 428 | // object is free to use the argument key to filter its 429 | // stringification. 430 | }; 431 | } 432 | 433 | // 15.9.4.2 Date.parse (string) 434 | // 15.9.1.15 Date Time String Format 435 | // Date.parse 436 | // based on work shared by Daniel Friesen (dantman) 437 | // http://gist.github.com/303249 438 | if (isNaN(Date.parse("T00:00"))) { 439 | // XXX global assignment won't work in embeddings that use 440 | // an alternate object for the context. 441 | Date = (function(NativeDate) { 442 | 443 | // Date.length === 7 444 | var Date = function(Y, M, D, h, m, s, ms) { 445 | var length = arguments.length; 446 | if (this instanceof NativeDate) { 447 | var date = length === 1 && String(Y) === Y ? // isString(Y) 448 | // We explicitly pass it through parse: 449 | new NativeDate(Date.parse(Y)) : 450 | // We have to manually make calls depending on argument 451 | // length here 452 | length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) : 453 | length >= 6 ? new NativeDate(Y, M, D, h, m, s) : 454 | length >= 5 ? new NativeDate(Y, M, D, h, m) : 455 | length >= 4 ? new NativeDate(Y, M, D, h) : 456 | length >= 3 ? new NativeDate(Y, M, D) : 457 | length >= 2 ? new NativeDate(Y, M) : 458 | length >= 1 ? new NativeDate(Y) : 459 | new NativeDate(); 460 | // Prevent mixups with unfixed Date object 461 | date.constructor = Date; 462 | return date; 463 | } 464 | return NativeDate.apply(this, arguments); 465 | }; 466 | 467 | // 15.9.1.15 Date Time String Format 468 | var isoDateExpression = new RegExp("^" + 469 | "(?:" + // optional year-month-day 470 | "(" + // year capture 471 | "(?:[+-]\\d\\d)?" + // 15.9.1.15.1 Extended years 472 | "\\d\\d\\d\\d" + // four-digit year 473 | ")" + 474 | "(?:-" + // optional month-day 475 | "(\\d\\d)" + // month capture 476 | "(?:-" + // optional day 477 | "(\\d\\d)" + // day capture 478 | ")?" + 479 | ")?" + 480 | ")?" + 481 | "(?:T" + // hour:minute:second.subsecond 482 | "(\\d\\d)" + // hour capture 483 | ":(\\d\\d)" + // minute capture 484 | "(?::" + // optional :second.subsecond 485 | "(\\d\\d)" + // second capture 486 | "(?:\\.(\\d\\d\\d))?" + // milisecond capture 487 | ")?" + 488 | ")?" + 489 | "(?:" + // time zone 490 | "Z|" + // UTC capture 491 | "([+-])(\\d\\d):(\\d\\d)" + // timezone offset 492 | // capture sign, hour, minute 493 | ")?" + 494 | "$"); 495 | 496 | // Copy any custom methods a 3rd party library may have added 497 | for (var key in NativeDate) 498 | Date[key] = NativeDate[key]; 499 | 500 | // Copy "native" methods explicitly; they may be non-enumerable 501 | Date.now = NativeDate.now; 502 | Date.UTC = NativeDate.UTC; 503 | Date.prototype = NativeDate.prototype; 504 | Date.prototype.constructor = Date; 505 | 506 | // Upgrade Date.parse to handle the ISO dates we use 507 | // TODO review specification to ascertain whether it is 508 | // necessary to implement partial ISO date strings. 509 | Date.parse = function(string) { 510 | var match = isoDateExpression.exec(string); 511 | if (match) { 512 | match.shift(); // kill match[0], the full match 513 | // recognize times without dates before normalizing the 514 | // numeric values, for later use 515 | var timeOnly = match[0] === undefined; 516 | // parse numerics 517 | for (var i = 0; i < 10; i++) { 518 | // skip + or - for the timezone offset 519 | if (i === 7) 520 | continue; 521 | // Note: parseInt would read 0-prefix numbers as 522 | // octal. Number constructor or unary + work better 523 | // here: 524 | match[i] = +(match[i] || (i < 3 ? 1 : 0)); 525 | // match[1] is the month. Months are 0-11 in JavaScript 526 | // Date objects, but 1-12 in ISO notation, so we 527 | // decrement. 528 | if (i === 1) 529 | match[i]--; 530 | } 531 | // if no year-month-date is provided, return a milisecond 532 | // quantity instead of a UTC date number value. 533 | if (timeOnly) 534 | return ((match[3] * 60 + match[4]) * 60 + match[5]) * 1000 + match[6]; 535 | 536 | // account for an explicit time zone offset if provided 537 | var offset = (match[8] * 60 + match[9]) * 60 * 1000; 538 | if (match[6] === "-") 539 | offset = -offset; 540 | 541 | return NativeDate.UTC.apply(this, match.slice(0, 7)) + offset; 542 | } 543 | return NativeDate.parse.apply(this, arguments); 544 | }; 545 | 546 | return Date; 547 | })(Date); 548 | } 549 | 550 | // 551 | // Function 552 | // ======== 553 | // 554 | 555 | // ES-5 15.3.4.5 556 | // http://www.ecma-international.org/publications/files/drafts/tc39-2009-025.pdf 557 | var slice = Array.prototype.slice; 558 | if (!Function.prototype.bind) { 559 | Function.prototype.bind = function (that) { // .length is 1 560 | // 1. Let Target be the this value. 561 | var target = this; 562 | // 2. If IsCallable(Target) is false, throw a TypeError exception. 563 | // XXX this gets pretty close, for all intents and purposes, letting 564 | // some duck-types slide 565 | if (typeof target.apply != "function" || typeof target.call != "function") 566 | return new TypeError(); 567 | // 3. Let A be a new (possibly empty) internal list of all of the 568 | // argument values provided after thisArg (arg1, arg2 etc), in order. 569 | var args = slice.call(arguments); 570 | // 4. Let F be a new native ECMAScript object. 571 | // 9. Set the [[Prototype]] internal property of F to the standard 572 | // built-in Function prototype object as specified in 15.3.3.1. 573 | // 10. Set the [[Call]] internal property of F as described in 574 | // 15.3.4.5.1. 575 | // 11. Set the [[Construct]] internal property of F as described in 576 | // 15.3.4.5.2. 577 | // 12. Set the [[HasInstance]] internal property of F as described in 578 | // 15.3.4.5.3. 579 | // 13. The [[Scope]] internal property of F is unused and need not 580 | // exist. 581 | var bound = function () { 582 | 583 | if (this instanceof bound) { 584 | // 15.3.4.5.2 [[Construct]] 585 | // When the [[Construct]] internal method of a function object, 586 | // F that was created using the bind function is called with a 587 | // list of arguments ExtraArgs the following steps are taken: 588 | // 1. Let target be the value of F's [[TargetFunction]] 589 | // internal property. 590 | // 2. If target has no [[Construct]] internal method, a 591 | // TypeError exception is thrown. 592 | // 3. Let boundArgs be the value of F's [[BoundArgs]] internal 593 | // property. 594 | // 4. Let args be a new list containing the same values as the 595 | // list boundArgs in the same order followed by the same 596 | // values as the list ExtraArgs in the same order. 597 | 598 | var self = Object.create(target.prototype); 599 | target.apply(self, args.concat(slice.call(arguments))); 600 | return self; 601 | 602 | } else { 603 | // 15.3.4.5.1 [[Call]] 604 | // When the [[Call]] internal method of a function object, F, 605 | // which was created using the bind function is called with a 606 | // this value and a list of arguments ExtraArgs the following 607 | // steps are taken: 608 | // 1. Let boundArgs be the value of F's [[BoundArgs]] internal 609 | // property. 610 | // 2. Let boundThis be the value of F's [[BoundThis]] internal 611 | // property. 612 | // 3. Let target be the value of F's [[TargetFunction]] internal 613 | // property. 614 | // 4. Let args be a new list containing the same values as the list 615 | // boundArgs in the same order followed by the same values as 616 | // the list ExtraArgs in the same order. 5. Return the 617 | // result of calling the [[Call]] internal method of target 618 | // providing boundThis as the this value and providing args 619 | // as the arguments. 620 | 621 | // equiv: target.call(this, ...boundArgs, ...args) 622 | return target.call.apply( 623 | target, 624 | args.concat(slice.call(arguments)) 625 | ); 626 | 627 | } 628 | 629 | }; 630 | // 5. Set the [[TargetFunction]] internal property of F to Target. 631 | // extra: 632 | bound.bound = target; 633 | // 6. Set the [[BoundThis]] internal property of F to the value of 634 | // thisArg. 635 | // extra: 636 | bound.boundTo = that; 637 | // 7. Set the [[BoundArgs]] internal property of F to A. 638 | // extra: 639 | bound.boundArgs = args; 640 | bound.length = ( 641 | // 14. If the [[Class]] internal property of Target is "Function", then 642 | typeof target == "function" ? 643 | // a. Let L be the length property of Target minus the length of A. 644 | // b. Set the length own property of F to either 0 or L, whichever is larger. 645 | Math.max(target.length - args.length, 0) : 646 | // 15. Else set the length own property of F to 0. 647 | 0 648 | ) 649 | // 16. The length own property of F is given attributes as specified in 650 | // 15.3.5.1. 651 | // TODO 652 | // 17. Set the [[Extensible]] internal property of F to true. 653 | // TODO 654 | // 18. Call the [[DefineOwnProperty]] internal method of F with 655 | // arguments "caller", PropertyDescriptor {[[Value]]: null, 656 | // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: 657 | // false}, and false. 658 | // TODO 659 | // 19. Call the [[DefineOwnProperty]] internal method of F with 660 | // arguments "arguments", PropertyDescriptor {[[Value]]: null, 661 | // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: 662 | // false}, and false. 663 | // TODO 664 | // NOTE Function objects created using Function.prototype.bind do not 665 | // have a prototype property. 666 | // XXX can't delete it in pure-js. 667 | return bound; 668 | }; 669 | } 670 | 671 | // 672 | // String 673 | // ====== 674 | // 675 | 676 | // ES5 15.5.4.20 677 | if (!String.prototype.trim) { 678 | // http://blog.stevenlevithan.com/archives/faster-trim-javascript 679 | var trimBeginRegexp = /^\s\s*/; 680 | var trimEndRegexp = /\s\s*$/; 681 | String.prototype.trim = function () { 682 | return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); 683 | }; 684 | } 685 | 686 | // 687 | // JSON 688 | // ==== 689 | // 690 | 691 | // require("json"); 692 | --------------------------------------------------------------------------------