十一月 25th, 2009

为TextField添加上tab缩进功能

5 Comments, JAse, as、flex, by army8735.

今天尝试为JAse的代码输入框添加tab缩进功能,华丽地失败了。仔细查阅下手册:

指定此对象是否遵循 Tab 键顺序。 如果该对象遵循 Tab 键顺序,值为 true;否则值为 false。 默认情况下,值为 false,但以下情况除外:

  • 对于 SimpleButton 对象,值为 true。
  • 对于具有 type = “input” 的 TextField 对象,值为 true。
  • 对于具有 buttonMode = true 的 Sprite 对象或 MovieClip 对象,值为 true。

原来如此,侦听TextField的按键事件,根本就判断不到是否是tab键,因为默认系统就屏蔽了。这样就无法改写默认响应方法,tab键变成了在按钮之间切换选择状态。

得儿,得罪不起你,去请你的上司吧。直接侦听stage的按键事件,改写其默认行为搞定。唯一有点不友好的地方是聚焦会先出现在按钮上,然后由改写的行为迅速回到输入框上并进行缩进。没办法,也只好这样了。

//全局侦听tag键,改变默认行为
addEventListener(Event.ADDED_TO_STAGE, function() {
	stage.addEventListener(KeyboardEvent.KEY_DOWN, function(event:KeyboardEvent):void {
		if (stage.focus == tf && event.keyCode == Keyboard.TAB) {
			saveInput();
			getSelection();
			//如果鼠标为选择区域,则先保存删除命令链
			if (left != right) {
				saveDelete();
			}
			//增加2个全角空格,增加输入命令链
			commandList.addCommand(new InputCommand(tf, tf.caretIndex, "\u3000\u3000"));
			tf.replaceSelectedText("\u3000\u3000");
			setIndex();
			focus();
		}
	});
	stage.addEventListener(KeyboardEvent.KEY_UP, function(event:KeyboardEvent):void {
		if (event.keyCode == Keyboard.TAB) {
			focus();
		}
	});
});

十一月 24th, 2009

说说最近在开发的JAse语法编辑器

No Comments, JAse, by army8735.

最近时间比较少,年底之前公布JAse预览版的可能性看来不高了,那就拖到农历年底之前吧,XD~

主要想说的是JAse进展得益于JAte的经验非常多,若不是之前有过长时间的尝试经验(尽管JAte作为实验品失败了),JAse不可能一帆风顺地进行过来。曾经读到一篇文章——《趁年轻,赶紧去做该做的傻事》,里面德鲁克的话让我感受颇深:

“我绝对不会把从未犯错的人升到高层领导岗位上,因为没有犯过大错的人必然是平庸之辈。更糟糕的是,没有犯过错的人将不会学到如何及早找出错误,并且改正错误。在工作中,如果一个同事谈论他的成功,我只会谨慎的倾听,小心的吸取;但如果一个人介绍过去的失误,我一定会竖起耳朵,绝不放过,因为这样的人往往经验丰富,思考全面,在失误基础上的成功也是最迷人的成功。(所以,在简历中,我们是否应该写上自己曾经做过哪些傻事?)”

JAse就是如此,若没有jssc的5版开发经历、没有JAte的研究结果,它就不会出现在我的手中了。至于JAte,最主要的失败原因是对flash对文本支持的不了解,flash对文本的支持实在有限,而图文混排更是可怜。我想得等as4时代来临,flash对富文本的支持才会好些吧。不过我倒是和KindEditor的作者Roddy经常聊天,也一直在催他(现在又要催了!哈哈!)把KE可视化原理写出来!或许以后JAte会变成基于KE(LGPL)用as制作toolbar,iframe做content的样式。

附上缓慢进展的JAse预览图一张,可以看到ui方面受以前影响颇多:

jase

PS:JAte的图标取自于Open Office,JAse的图标取自于Flash Develop

十一月 18th, 2009

《Project Darkstar》中文文档

No Comments, 翻译, by army8735.

和我同住的某人开始把翻译好的《Project Darkstar》中文文档上传至Blog了,在这里宣传转一下。

地址:http://www.david0446.com/?p=6

十一月 16th, 2009

动态加载css和js资源

No Comments, 前端开发, by army8735.

近日,在秦歌的blog中看到动态加载的内容,主要是说YUI3.0在动态加载js的一点小bug。当时想了个问题:如何侦听动态加载的内容被成功加载了,翻阅一番后记录下来。

Omar AL Zabir曾在他的Ensure library库中写过类似的东西,用以动态加载css和js文件。加载js时,基本伪代码是这样:

var script = document.createElement("script");
script.onreadystatechange = function() {
	//
}
script.onload = function () {
	//
}
script.setAttribute("src", "xxx.js");
document.head.appendChild(script);

原理就是创建一个script标签,设置src属性后附加到head区域。ie中侦听onreadystatechange,其它的侦听onload。不过有些较古老的浏览器对这两种方法都不支持(Omar举例为Safari 2),解决的办法是通过xhr加载js文件资源,再eval()出来。目前来看,似乎已没必要花费经历去为了旧版本兼容了。

凑巧,前几日Mootools官网也发布了一个延迟独立加载js的库Depender,它被包括在More中(貌似1.2.4.1还有些性能问题,我希望最终它能够被包含在Core中)。看看其核心实现:

new Element('script', {
	src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
	events: {
		load: loaded,
		readystatechange: function(){
			if (['loaded', 'complete'].contains(this.readyState)) loaded();
		},
		error: error
	}
}).inject(this.options.target || document.head);

亦是一样。不过在我测试过程中,ie的readystate的值在加载完成后都是loaded(ie6、7、8),并没有出现complete的情况,不知道mt为何要多判别它。

另外关于css动态加载,似乎并没有可行的侦听的方法。虽然ie中侦听onreadystatechange的值为complete,但是firefox 3.5和chrome里却侦听不到onload事件。不过一般情况下,样式的加载都是异步的,同步的需求很少。除非考虑到某个地方必须等样式加载成功后才去执行显示,否则没必要使用同步。

我能想到的解决办法就是通过xhr读取css文件内容,然后插入到style标签中来实现同步读取,不知道可不可行。

区分正则

想要高亮正则表达式的前提是区分它。由于正则表达式以/开头,因此这和除法、单行注释、多行注释会产生混淆。那么究竟该如何辨别呢?

jssc-article-3-1

注释的区别最为简单,因为注释拥有最高优先级。除非注释符出现在字符串内,否则一旦开始,编译器便立刻识别///*

如图所示,当向前看字符为斜线时,根据规则由初始状态0进入状态1。如果接下来读入的字符是*,则进入状态2,并成为多行注释;倘若是/,则进入状态3,变成单行注释;而其它的情况么,则是正则除法皆有可能。

我们不妨设想一下:正则和除法有哪些区别?从语境上说,原则性的区别就是除法的前面是被除数,正则的前面不是。而成为被除数的可能无非是以下3种:数字、变量、括号内的运算结果。我不知道js解析引擎是如何做的,它可能相当得严谨(也简单,因为它会忽略空白和注释)。但是在web端语法高亮的需求中,由于假设输入的代码一定是正确的,所以可以“投机取巧”般地识别二者:

//检查当前除号是否是perl风格正则表达式开头
protected function isPerlReg():Boolean {
	for (var j:int = index - 3; j > -1; j--) {
		//先要忽略之前的单行注释
		if (j == singleCommentEnd) {
			j = singleCommentStart;
			continue;
		}
		var cc:int = code.charCodeAt(j);
		//忽略空白
		if (Character.isBlank(cc) || Character.isLine(cc)) {
			continue;
		}
		//前面也是一个除号的话防止是多行结尾注释,需跳过注释段继续向前找
		else if (Character.isSlash(cc) && Character.isStar(code.charCodeAt(j - 1))) {
			j = code.lastIndexOf("/*", j - 1);
		}
		//前面一个非空白字符若是字母数字下划线则为正则,否则除号
		else if (Character.isLetterOrDigit(cc) || Character.isUnderline(cc) || Character.isDollar(cc) || Character.isRightParenthese(cc)) {
			return false;
		}
		else {
			break;
		}
	}
	return true;
}

程序开始回溯,我们要看在斜线符号之前的“东西”到底是什么——当然,空白符是不算在内的,因此循环体内首先要做的就是忽略空白。假如前面是标识符的组成或者右括号的话,那么显然它是个被除数,斜线也应该被解读为除号;而如果是其它情况的话,斜线则是正则表达式的开头。

除此外还有个很大的难点,就是斜线前面插入了一段注释,这也需要忽略(js引擎无需考虑这些,因为一开始注释就被剔除了,所以它会相对简单一些)。目前jssc 5 beta 3版仅能识别出多行注释,对于单行注释尚未解决,这是下个版本需要注意的问题。

注:新版本已从语法层级上解决,方法为设置一个布尔标量,每次处理token时切换其正确状态,上述代码在新版本中已废弃。

识别正则

区分出来之后别是识别正则,根据特点我们画出这样的DFA:

jssc-article-3-2

我们假设当前/已经被识别为正则表达式的开头,那么接下来的任务便是寻找另外一个结尾/

在状态1中首先要明确对待的就是转义符,因为它会转义掉下面一个符号,所以要单独为其区分出一个状态2。而状态2上的条件就很宽松了:无论输入什么都会被转义掉,因此遇到任何字符(any)都会回到状态1。

值得注意的是,字符集([])是个特殊之处,因为它里面可以省略转义符(既可以有也可以没有),为此需要画出状态3和状态4来对待它(字符集中出现未被转义的/也是合法的,你不可能把它当作正则表达式的结束符)。

当真正出现结束符后进入状态5,此时正则并没有完全结束,因为后面还可能跟有譬如i这样的flag。在状态5上多加种条件循环会本身便可解决问题(更严谨的做法应该是flag仅允许出现一次);而其它情况才是最终进入终结状态6。

//perl正则
protected function dealPerlReg():void {
	var start:int = index - 2;
	var cc:int;
	outer:
	while (index <= code.length) {
		readch();
		cc = peek.charCodeAt(0);
		//转义符
		if (Character.isBackslash(cc)) {
			readch();
			//后面是换行跳出
			if (Character.isLine(peek.charCodeAt(0))) {
				break;
			}
		}
		//[括号
		else if (Character.isLeftbracket(cc)) {
			while (index <= code.length) {
				readch();
				cc = peek.charCodeAt(0);
				//转义符
				if (Character.isBackslash(cc)) {
					readch();
					if (Character.isLine(peek.charCodeAt(0))) {
						break outer;
					}
				}
				//]括号
				else if (Character.isRightbracket(cc)) {
					continue outer;
				}
			}
		}
		//行末尾
		else if (Character.isLine(cc)) {
			break;
		}
		//正则表达式/结束
		else if (Character.isSlash(cc)) {
			for (var i:int = 0; i < 3 && index <= code.length; i++) {
				readch();
				cc = peek.charCodeAt(0);
				//是img中的一个,存入flag中,不分大小写
				if(cc == 103 || cc == 105 || cc == 109 || cc == 71 || cc == 73 || cc == 77) {
					//
				}
				//其它情况跳出
				else {
					break outer;
				}
			}
			break;
		}
	}
	//高亮
	result.append(HighLighter.regular(HtmlEscape.encode(code.slice(start, index - 1))));
}

其它

其它符号的处理都很简单,不做任何高亮即可,只是要注意个别需要特殊编码的字符(如&变为&amp;)。在编译器的词法分析中,每个符号都是有语法意义的,有时符号的长度还不止一个(<=就有2个符号)。幸运的是,在做语法高亮的情况下,这些全可以被“偷懒”掉。

下面贴一下c系列语言处理符号的代码,它相当得简单,可能唯一特殊之处就是其中对花括弧的深度处理了。

//抽象类中的处理符号方法
protected function dealSign():void {
	result.append(HtmlEscape.encodeChar(peek));
	readch();
}

//c系列语言继承后覆盖的方法,处理符号同时要计算深度
protected override function dealSign():void {
	var cc:int = peek.charCodeAt(0);
	if (Character.isLeftBrace(cc)) {
		depth++;
	}
	else if (Character.isRightBrace(cc)) {
		depth--;
	}
	super.dealSign();
}

下一篇中,我将讲述jssc 5是如何做到嵌套高亮(html中内嵌css和js)的。