おもしろく生きたいね

2007年11月5日の記事一覧

 エンコーディング/ロカール判定をまともにしてみた。[2007-11-06改訂](download)

Index: NSStringITerm.m
===================================================================
RCS file: /cvsroot/iterm/iTerm/NSStringITerm.m,v
retrieving revision 1.8
diff -u -r1.8 NSStringITerm.m
--- NSStringITerm.m	13 Nov 2006 08:01:04 -0000	1.8
+++ NSStringITerm.m	6 Nov 2007 09:24:28 -0000
@@ -294,9 +294,92 @@
return [NSString stringWithFormat:@"%d", num];
}
++ (BOOL)isCJKEncoding:(NSStringEncoding)encoding
+{
+    static NSMutableDictionary *isEncodingCJK = nil; // cache for encoding to isCJK mapping
+    static NSStringEncoding previousEncoding = 1; // ASCII
+    static BOOL isCJK = NO;
+    NSNumber *key, *val;
+    const char *lang;
+
+    if (encoding == previousEncoding) {
+        //NSLog(@"encoding[0x%08lx] is %s, again", encoding, isCJK ? "CJK" : "not CJK");
+        return isCJK;
+    }
+
+    previousEncoding = encoding;
+
+    key = [NSNumber numberWithUnsignedInt:encoding];
+
+    if (isEncodingCJK == nil) {
+        isEncodingCJK = [[NSMutableDictionary alloc] init];
+    }
+    else {
+        val = [isEncodingCJK objectForKey:key];
+
+        if (val != nil) {
+            isCJK = [val boolValue];
+            //NSLog(@"encoding[0x%08lx] is %s, IIRC", encoding, isCJK ? "CJK" : "not CJK");
+            return isCJK;
+        }
+    }
+
+    switch (encoding) {
+      // Simplified Chinese
+      case 0x80000019: // Mac
+      case 0x80000421: // Windows
+      case 0x80000631: // GBK
+      case 0x80000632: // GB 18030
+      case 0x80000930: // EUC
+      // Traditional Chinese
+      case 0x80000002: // Mac
+      case 0x80000423: // Windows
+      case 0x80000931: // EUC
+      case 0x80000A03: // Big5
+      case 0x80000A06: // Big5 HKSCS
+      // Japanese
+      case 0x00000003: // EUC
+      case 0x00000008: // Windows
+      case 0x00000015: // ISO-2022-JP
+      case 0x80000001: // Mac
+      case 0x80000628: // Shift JIS X0213
+      case 0x80000A01: // Shift JIS
+      // Korean
+      case 0x80000003: // Mac
+      case 0x80000422: // Windows
+      case 0x80000840: // ISO-2022-KR
+      case 0x80000940: // EUC
+        isCJK = YES;
+        //NSLog(@"0x%08lx is known to be %s", encoding, isCJK ? "CJK" : "not CJK");
+        break;
+
+      case 0x00000004: // UTF-8
+        isCJK = ((lang = getenv("LC_ALL")) != NULL ||
+                 (lang = getenv("LC_CTYPE")) != NULL ||
+                 (lang = getenv("LANG")) != NULL) &&
+            strlen(lang) >= 3 &&
+            (!strncmp(lang, "ja_", 3) ||
+             !strncmp(lang, "kr_", 3) ||
+             !strncmp(lang, "zh_", 3));
+        //NSLog(@"locale[%s] looks %s", lang, isCJK ? "CJK" : "not CJK");
+        break;
+
+      default:
+        isCJK = NO;
+        //NSLog(@"encoding[0x%08lx] is not known to be CJK", encoding);
+        break;
+    }
+
+    // Store in cache
+    val = [NSNumber numberWithBool:isCJK];
+    [isEncodingCJK setObject:val forKey:key];
+
+    return isCJK;
+}
+
+ (BOOL)isDoubleWidthCharacter:(unichar)unicode encoding:(NSStringEncoding) e
{
-	if (unicode <= 0xa0 || (unicode>0x452 && unicode <0x200f))
+	if (unicode <= 0xa0 || (unicode>0x452 && unicode <0x1100))
return NO;
/*
unicode character width check
@@ -325,11 +408,7 @@
/* Ambiguous ones */
-	if ((e)==0x80000019||(e)==0x80000421||(e)==0x80000631||(e)==0x80000632||(e)==0x80000930 || //GB
-		(e)==0x80000002||(e)==0x80000423||(e)==0x80000931||(e)==0x80000a03||(e)==0x80000a06 || //BIG5
-		(e)==0x80000001||(e)==0x8||(e)==0x15 || //JP
-		(e)==0x80000628||(e)==0x80000a01 || //SJIS
-		(e)==0x80000422||(e)==0x80000003||(e)==0x80000840||(e)==0x80000940) //KR
+	if ([self isCJKEncoding:e])
{
if ((unicode >=0xfe00 && unicode <=0xfe0f) ||
(unicode >=0x2776 && unicode <=0x277f) ||

これならsubmit可能か。ただ、描画部分でASCII/non-ASCIIフォントの選択を誤るのでまだ不完全。また時間が取れたら見ます。

 あと、下線が倍幅に対応していない。(download)

Index: PTYTextView.m
===================================================================
RCS file: /cvsroot/iterm/iTerm/PTYTextView.m,v
retrieving revision 1.307
diff -u -r1.307 PTYTextView.m
--- PTYTextView.m	17 Jun 2007 01:56:31 -0000	1.307
+++ PTYTextView.m	6 Nov 2007 02:07:04 -0000
@@ -963,7 +963,7 @@
//draw underline
if (theLine[j].fg_color & UNDER_MASK && theLine[j].ch) {
[[self colorForCode:(fgcode & 0x1ff)] set];
-						NSRectFill(NSMakeRect(curX,curY-2,charWidth,1));
+						NSRectFill(NSMakeRect(curX,curY-2,double_width?charWidth*2:charWidth,1));
}
}
}

 ほかにも、あんまり致命的ではないが特定のケースでカーソル移動によって文字の右半分が欠けたりする。まあ、ソースがあるからいつか捕まえて直せるだろう。Terminal.appもソースを公開してほしいね。


 LeopardのTerminal.appをしばらく使っているうちに、こいつは使えない代物だと分かった。

 まず、「○」とか「△」とかの記号を倍幅文字と認識してくれない。これは再描画しても直らないのでTigerのTerminal.appよりひどい。もしかすると、日本語フォントの記号グリフに沿った文字幅情報を含むエンコーディング定義を追加することできれいに対処できるのかもしれないが、その方法は未調査。ていうか、こんなのデベロッパープレビュー段階で気づかなかったんだろうか。Appleには日本人ハッカーはいないの?

 二番目に、CommandキーをMetaキーとして使えないのが不便。TigerではAPE+Cmd2Optでなんとかなったが、今のところLeopardには対応していない。ていうかCmd2Optの配布物ってWeb Archive等を漁らないと手に入らなかった気がする。なんにせよ、Emacs使いにとってMetaキーが左下のちっこいOptionキーひとつというのは致命的だ。小指をつって死ねと言うに等しい。小指が休職したら、Ctrl+[で亀のようなもたもた人生を遅れというのか。

 あと、ANSIカラーを調整できないので目に優しくない。頼みの綱はTerminalColorsだが、作者のサイトを見るとLeopardのTerminal.appに対応するのは当分先になりそうだ。

 どうにかしようにもソースがない。じゃあソースがあるものをいじろう、ということでiTermに手を入れました。まず、第一の記号問題を解決するパッチがこれ。(download)

Index: NSStringITerm.m
===================================================================
RCS file: /cvsroot/iterm/iTerm/NSStringITerm.m,v
retrieving revision 1.8
diff -u -r1.8 NSStringITerm.m
--- NSStringITerm.m	13 Nov 2006 08:01:04 -0000	1.8
+++ NSStringITerm.m	5 Nov 2007 14:04:20 -0000
@@ -286,6 +286,8 @@
 	0xfffd,
 };

+static int isJapaneseEnvironment = -1;
+

 @implementation NSString (iTerm)

@@ -296,8 +298,22 @@

 + (BOOL)isDoubleWidthCharacter:(unichar)unicode encoding:(NSStringEncoding) e
 {
-	if (unicode <= 0xa0 || (unicode>0x452 && unicode <0x200f))
-		return NO;
+    if (unicode <= 0xa0 || (unicode>0x452 && unicode <0x200f))
+        return NO;
+
+    if (isJapaneseEnvironment < 0) {
+        const char ja_JP[] = "ja_JP.";
+        const char *lang = getenv("LANG");
+
+        if (lang != NULL && strlen(lang) > sizeof(ja_JP) - 1 && !strncmp(lang, ja_JP, sizeof(ja_JP) - 1))
+            isJapaneseEnvironment = YES;
+        else
+            isJapaneseEnvironment = NO;
+    }
+
+    if (isJapaneseEnvironment && unicode >= 0x2000)
+        return YES;
+
     /*
      unicode character width check
      see. http://www.unicode.org

かなり手抜きながら、LANGja_JPっぽいときはU+2000以上の文字は倍幅とみなすものだ。

 次に第二のCommandキーをMetaキーにするパッチがこれ。(download)

Index: iTermApplication.m
===================================================================
RCS file: /cvsroot/iterm/iTerm/iTermApplication.m,v
retrieving revision 1.10
diff -u -r1.10 iTermApplication.m
--- iTermApplication.m	7 Nov 2006 08:03:08 -0000	1.10
+++ iTermApplication.m	5 Nov 2007 13:51:06 -0000
@@ -43,6 +43,10 @@
 	id aWindow;
 	PseudoTerminal *currentTerminal;
 	PTYSession *currentSession;
+	unsigned int modflag;
+	unichar unicode;
+	NSString *keystr;
+	NSEvent *newEvent;

 	if([anEvent type] == NSKeyDown)
@@ -57,16 +61,42 @@
 			currentSession = [currentTerminal currentSession];

 			if([currentSession hasKeyMappingForEvent: anEvent highPriority: YES])
+			{
 				[currentSession keyDown: anEvent];
-			else
-				[super sendEvent: anEvent];
-		}
-		else
-		   [super sendEvent: anEvent];
+				return;
+			}
+
+			modflag = [anEvent modifierFlags];
+
+			if ((modflag & NSCommandKeyMask)) {
+				keystr  = [anEvent characters];
+				unicode = [keystr length] > 0 ? [keystr characterAtIndex : 0] : 0;

+				switch (unicode)
+				{
+					case 0x20:						// Switch Input Source
+					case NSCarriageReturnCharacter:	// Enter Full Screen
+						break;
+
+					default:
+						newEvent = [NSEvent keyEventWithType: [anEvent type]
+											location: [anEvent locationInWindow]
+											modifierFlags: ((modflag - NSCommandKeyMask) | NSAlternateKeyMask)
+											timestamp: [anEvent timestamp]
+											windowNumber: [anEvent windowNumber]
+											context: [anEvent context]
+											characters: keystr
+											charactersIgnoringModifiers: [anEvent charactersIgnoringModifiers]
+											isARepeat: [anEvent isARepeat]
+											keyCode: [anEvent keyCode]];
+						[currentSession keyDown: newEvent];
+						return;
+				}
+			}
+		}
 	}
-	else
-		[super sendEvent: anEvent];
+
+	[super sendEvent: anEvent];
 }

 @end

これは、一部の組合せを除き、CommandキーをOptionキーと同一視させる修正。もちろん、Keyboard Profileの「Option Key as」でOptionキーをMetaキーにしないと無意味だ。ふつうは日本語を直接入力したいだろうから、そこの設定は「Meta」ではなく「+Esc」にするだろう。

 なお、第三の色問題はiTermには存在しない。Display Profileでいじれるから。

 iTermのサイトからCVSでソースを取ってきて上のパッチを当て、好みに応じていじってビルドしてください。特にどのCommandキーショートカットを生かすかの部分は人それぞれのはず。ビルド方法は、iTerm.xcodeprojを開いてビルドボタンを押すだけ。Xcodeは最新版(Tigerなら2.5, Leopardなら3.0)にしておいた方がいい。Tiger 10.4.10 + Xcode 2.4だとうまくビルドできなかった。

 何か情報があれば寄せてください。みんな我慢しているのか、他にいい選択肢があって俺が知らないだけなのか、いつも不思議なんだよね。

 次に続く。