├── 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{\tLorem 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]+),:left)
176 | # You don't have to reverse your regex when matching text after the caret
177 | assert_equal 'p', Word.current_word(/<\/([-:a-z]+)/,:right)
178 | end
179 |
180 | end
181 | end
182 |
--------------------------------------------------------------------------------
/Support/lib/Suggestion.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 | name : Suggestion
4 | description : Dynamically create CodeCompletion lists for use with TextMate and Thomas Aylott's (SubtleGradient) `complete.rb`
5 | version: 0.1
6 |
7 | authors : Thomas Aylott
8 | copyright : © 2010 Thomas Aylott
9 | license : MIT
10 |
11 | provides :
12 | - Suggestion
13 | - Suggestion.Method
14 | - Suggestion.Property
15 |
16 | requires :
17 | - JSON
18 | - Object.create
19 | - Snippet
20 | ...
21 | */
22 |
23 | if (typeof require !== 'undefined'){
24 | try {
25 | var Snippet = require('Snippet').Snippet;
26 | require('global-es5');
27 | require('string');
28 | } catch(e){}
29 | }
30 |
31 | Suggestion.completions = {
32 |
33 | extra_chars: "\._$",
34 | images: {
35 | // Namespace:'Namespace.png',
36 | // Method:'Method.png',
37 | // Property:'Property.png'
38 | },
39 | tool_tip_prefix: "",
40 | suggestions:[]
41 |
42 | };
43 |
44 | function Suggestion(options){
45 | if (!(this instanceof Suggestion)) return new Suggestion(options);
46 |
47 | if (options) for (var property in options){
48 | if (!hasOwnProperty.call(options, property)) continue;
49 | if (typeof options[property] === 'undefined') continue;
50 | this[property] = options[property];
51 | }
52 |
53 | this.insert = (new Snippet()).appendRaw(this.insert);
54 | }
55 | Suggestion.prototype = {
56 | tool_tip_format : "html",
57 | tool_tip : "",
58 | insert : "$0", // new Snippet()
59 | match : "suggestion",
60 | display : "My suggestion of Doom!",
61 | image : "",
62 |
63 | toJSON: function(){
64 | return {
65 | tool_tip_format : "" + this.tool_tip_format ,
66 | tool_tip : "" + this.tool_tip ,
67 | insert : "" + this.insert ,
68 | match : "" + this.match ,
69 | display : "" + this.display ,
70 | image : "" + this.image
71 | };
72 | }
73 | };
74 |
75 |
76 | Suggestion.Method = function(options){
77 | if (!(this instanceof Suggestion.Method)) return new Suggestion.Method(options);
78 | Suggestion.call(this, options);
79 | this.params = [];
80 | };
81 | Suggestion.Method.prototype = Object.create(Suggestion.prototype);
82 | Suggestion.Method.prototype.image = "Method";
83 | Suggestion.Method.prototype.insert = "($0)";
84 | Suggestion.Method.prototype.updateToolTip = function(){
85 | tip = '';
86 |
87 | tip += this.description.replace(/^\s*(
)?/,'
');
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 .*?>([^<]*))){
101 | objectKey = objectKey[1];
102 | objectProperties = this.API[objectKey].properties;
103 | // sys.p(objectKey, objectProperties);
104 |
105 | tip += "{\n"
106 | for (var p = -1; p < objectProperties.length; ++p){
107 | if (!objectProperties[p]) continue;
108 | tip += describeProperty(objectProperties[p]);
109 | }
110 | tip += "}\n"
111 | continue;
112 | }
113 |
114 | tip += "\t";
115 | tip += describeProperty(param);
116 | }
117 | tip += ")\n"
118 | tip += ''
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 |
--------------------------------------------------------------------------------