Project

General

Profile

1 458 doc
/*
2
 * CodePress - Real Time Syntax Highlighting Editor written in JavaScript - http://codepress.org/
3
 *
4
 * Copyright (C) 2007 Fernando M.A.d.S. <fermads@gmail.com>
5
 *
6
 * Contributors :
7
 *
8
 * 	Michael Hurni <michael.hurni@gmail.com>
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under the terms of the
11
 * GNU Lesser General Public License as published by the Free Software Foundation.
12
 *
13
 * Read the full licence: http://www.opensource.org/licenses/lgpl-license.php
14
 */
15
16
17
CodePress = {
18
	scrolling : false,
19
	autocomplete : true,
20
21
	// set initial vars and start sh
22
	initialize : function() {
23
		if(typeof(editor)=='undefined' && !arguments[0]) return;
24
		chars = '|32|46|62|'; // charcodes that trigger syntax highlighting
25
		cc = '\u2009'; // control char
26
		editor = document.getElementsByTagName('body')[0];
27
		editor.contentEditable = 'true';
28
		document.getElementsByTagName('body')[0].onfocus = function() {editor.focus();}
29
		document.attachEvent('onkeydown', this.metaHandler);
30
		document.attachEvent('onkeypress', this.keyHandler);
31
		window.attachEvent('onscroll', function() { if(!CodePress.scrolling) setTimeout(function(){CodePress.syntaxHighlight('scroll')},1)});
32
		completeChars = this.getCompleteChars();
33
//		CodePress.syntaxHighlight('init');
34
		setTimeout(function() { window.scroll(0,0) },50); // scroll IE to top
35
	},
36
37
	// treat key bindings
38
	keyHandler : function(evt) {
39
		charCode = evt.keyCode;
40
		if(completeChars.indexOf('|'+String.fromCharCode(charCode)+'|')!=-1 && CodePress.autocomplete) { // auto complete
41
			CodePress.complete(String.fromCharCode(charCode))
42
		}
43
	    else if(chars.indexOf('|'+charCode+'|')!=-1||charCode==13) { // syntax highlighting
44
		 	CodePress.syntaxHighlight('generic');
45
		}
46
	},
47
48
	metaHandler : function(evt) {
49
		keyCode = evt.keyCode;
50
		if(keyCode==9 || evt.tabKey) {
51
			CodePress.snippets();
52
		}
53
		else if((keyCode==122||keyCode==121||keyCode==90) && evt.ctrlKey) { // undo and redo
54
			(keyCode==121||evt.shiftKey) ? CodePress.actions.redo() :  CodePress.actions.undo();
55
			evt.returnValue = false;
56
		}
57
		else if(keyCode==34||keyCode==33) { // handle page up/down for IE
58
			self.scrollBy(0, (keyCode==34) ? 200 : -200);
59
			evt.returnValue = false;
60
		}
61
		else if(keyCode==46||keyCode==8) { // save to history when delete or backspace pressed
62
		 	CodePress.actions.history[CodePress.actions.next()] = editor.innerHTML;
63
		}
64
		else if((evt.ctrlKey || evt.metaKey) && evt.shiftKey && keyCode!=90)  { // shortcuts = ctrl||appleKey+shift+key!=z(undo)
65
			CodePress.shortcuts(keyCode);
66
			evt.returnValue = false;
67
		}
68
		else if(keyCode==86 && evt.ctrlKey)  { // paste
69
			// TODO: pasted text should be parsed and highlighted
70
		}
71
	},
72
73
	// put cursor back to its original position after every parsing
74
	findString : function() {
75
	    range = self.document.body.createTextRange();
76
		if(range.findText(cc)){
77
			range.select();
78
			range.text = '';
79
		}
80
	},
81
82
	// split big files, highlighting parts of it
83
	split : function(code,flag) {
84
		if(flag=='scroll') {
85
			this.scrolling = true;
86
			return code;
87
		}
88
		else {
89
			this.scrolling = false;
90
			mid = code.indexOf(cc);
91
			if(mid-2000<0) {ini=0;end=4000;}
92
			else if(mid+2000>code.length) {ini=code.length-4000;end=code.length;}
93
			else {ini=mid-2000;end=mid+2000;}
94
			code = code.substring(ini,end);
95
			return code.substring(code.indexOf('<P>'),code.lastIndexOf('</P>')+4);
96
		}
97
	},
98
99
	// syntax highlighting parser
100
	syntaxHighlight : function(flag) {
101
		if(flag!='init') document.selection.createRange().text = cc;
102
		o = editor.innerHTML;
103
		o = o.replace(/<P>/g,'\n');
104
		o = o.replace(/<\/P>/g,'\r');
105
		o = o.replace(/<.*?>/g,'');
106
		o = o.replace(/&nbsp;/g,'');
107
		o = '<PRE><P>'+o+'</P></PRE>';
108
		o = o.replace(/\n\r/g,'<P></P>');
109
		o = o.replace(/\n/g,'<P>');
110
		o = o.replace(/\r/g,'<\/P>');
111
		o = o.replace(/<P>(<P>)+/,'<P>');
112
		o = o.replace(/<\/P>(<\/P>)+/,'</P>');
113
		o = o.replace(/<P><\/P>/g,'<P><BR/></P>');
114
		x = z = this.split(o,flag);
115
116
		if(arguments[1]&&arguments[2]) x = x.replace(arguments[1],arguments[2]);
117
118
		for(i=0;i<Language.syntax.length;i++)
119
			x = x.replace(Language.syntax[i].input,Language.syntax[i].output);
120
121
		editor.innerHTML = this.actions.history[this.actions.next()] = (flag=='scroll') ? x : o.replace(z,x);
122
		if(flag!='init') this.findString();
123
	},
124
125
	snippets : function(evt) {
126
		var snippets = Language.snippets;
127
		var trigger = this.getLastWord();
128
		for (var i=0; i<snippets.length; i++) {
129
			if(snippets[i].input == trigger) {
130
				var content = snippets[i].output.replace(/</g,'&lt;');
131
				content = content.replace(/>/g,'&gt;');
132
				if(content.indexOf('$0')<0) content += cc;
133
				else content = content.replace(/\$0/,cc);
134
				content = content.replace(/\n/g,'</P><P>');
135
				var pattern = new RegExp(trigger+cc);
136
				this.syntaxHighlight('snippets',pattern,content);
137
			}
138
		}
139
	},
140
141
	readOnly : function() {
142
		editor.contentEditable = (arguments[0]) ? 'true' : 'false';
143
	},
144
145
	complete : function(trigger) {
146
		var complete = Language.complete;
147
		for (var i=0; i<complete.length; i++) {
148
			if(complete[i].input == trigger) {
149
				var pattern = new RegExp('\\'+trigger+cc);
150
				var content = complete[i].output.replace(/\$0/g,cc);
151
				setTimeout(function () { CodePress.syntaxHighlight('complete',pattern,content)},0); // wait for char to appear on screen
152
			}
153
		}
154
	},
155
156
	getCompleteChars : function() {
157
		var cChars = '';
158
		for(var i=0;i<Language.complete.length;i++)
159
			cChars += '|'+Language.complete[i].input;
160
		return cChars+'|';
161
	},
162
163
	shortcuts : function() {
164
		var cCode = arguments[0];
165
		if(cCode==13) cCode = '[enter]';
166
		else if(cCode==32) cCode = '[space]';
167
		else cCode = '['+String.fromCharCode(keyCode).toLowerCase()+']';
168
		for(var i=0;i<Language.shortcuts.length;i++)
169
			if(Language.shortcuts[i].input == cCode)
170
				this.insertCode(Language.shortcuts[i].output,false);
171
	},
172
173
	getLastWord : function() {
174
		var rangeAndCaret = CodePress.getRangeAndCaret();
175
		words = rangeAndCaret[0].substring(rangeAndCaret[1]-40,rangeAndCaret[1]);
176
		words = words.replace(/[\s\r\);]/g,'\n').split('\n');
177
		return words[words.length-1];
178
	},
179
180
	getRangeAndCaret : function() {
181
		var range = document.selection.createRange();
182
		var caret = Math.abs(range.moveStart('character', -1000000)+1);
183
		range = this.getCode();
184
		range = range.replace(/\n\r/gi,'  ');
185
		range = range.replace(/\n/gi,'');
186
		return [range.toString(),caret];
187
	},
188
189
	insertCode : function(code,replaceCursorBefore) {
190
		var repdeb = '';
191
		var repfin = '';
192
193
		if(replaceCursorBefore) { repfin = code; }
194
		else { repdeb = code; }
195
196
		if(typeof document.selection != 'undefined') {
197
			var range = document.selection.createRange();
198
			range.text = repdeb + repfin;
199
			range = document.selection.createRange();
200
			range.move('character', -repfin.length);
201
			range.select();
202
		}
203
	},
204
205
	// get code from editor
206
	getCode : function() {
207
		var code = editor.innerHTML;
208
		code = code.replace(/<br>/g,'\n');
209
		code = code.replace(/<\/p>/gi,'\r');
210
		code = code.replace(/<p>/i,''); // IE first line fix
211
		code = code.replace(/<p>/gi,'\n');
212
		code = code.replace(/&nbsp;/gi,'');
213
		code = code.replace(/\u2009/g,'');
214
		code = code.replace(/<.*?>/g,'');
215
		code = code.replace(/&lt;/g,'<');
216
		code = code.replace(/&gt;/g,'>');
217
		code = code.replace(/&amp;/gi,'&');
218
		return code;
219
	},
220
221
	// put code inside editor
222
	setCode : function() {
223
		var code = arguments[0];
224
		code = code.replace(/\u2009/gi,'');
225
		code = code.replace(/&/gi,'&amp;');
226
       	code = code.replace(/</g,'&lt;');
227
        code = code.replace(/>/g,'&gt;');
228
		editor.innerHTML = '<pre>'+code+'</pre>';
229
	},
230
231
232
	// undo and redo methods
233
	actions : {
234
		pos : -1, // actual history position
235
		history : [], // history vector
236
237
		undo : function() {
238
			if(editor.innerHTML.indexOf(cc)==-1){
239
				document.selection.createRange().text = cc;
240
			 	this.history[this.pos] = editor.innerHTML;
241
			}
242
			this.pos--;
243
			if(typeof(this.history[this.pos])=='undefined') this.pos++;
244
			editor.innerHTML = this.history[this.pos];
245
			CodePress.findString();
246
		},
247
248
		redo : function() {
249
			this.pos++;
250
			if(typeof(this.history[this.pos])=='undefined') this.pos--;
251
			editor.innerHTML = this.history[this.pos];
252
			CodePress.findString();
253
		},
254
255
		next : function() { // get next vector position and clean old ones
256
			if(this.pos>20) this.history[this.pos-21] = undefined;
257
			return ++this.pos;
258
		}
259
	}
260
}
261
262
Language={};
263
window.attachEvent('onload', function() { CodePress.initialize('new');});