Migrate UnicodeCharacter to Swift (#570)

This commit is contained in:
1024jp 2016-07-26 11:27:50 +09:00
parent ae17f6bc84
commit aa675bfbd5
19 changed files with 635 additions and 603 deletions

View File

@ -16,6 +16,7 @@ develop
- Drop support for __OS X Mountain Lion__ and __Mavericks__.
- Change the regular expression engine for find panel from the Onigmo to the ICU library.
- Update line-up of the search options.
- Inserting single surrogate character is no more valid.
- Remove “Not writable” alert which displayed on file opening.
- New acknowledgements window.
- Add help buttons to syntax style editor.

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11129.15" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11173.2" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11129.15"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11173.2"/>
</dependencies>
<objects>
<customObject id="-2" customClass="UnicodeInputPanelController" customModule="CotEditor" customModuleProvider="target">
@ -30,7 +30,7 @@
</allowedInputSourceLocales>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="unicode" id="wVY-6I-8j7">
<binding destination="-2" name="value" keyPath="codePoint" id="Lk1-2Z-Zx5">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
<string key="NSNullPlaceholder">U+1F600</string>
@ -65,16 +65,16 @@ DQ
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="character.name" id="rz0-Df-N9T">
<dictionary key="options">
<string key="NSNullPlaceholder">Invalid code</string>
</dictionary>
</binding>
<binding destination="-2" name="hidden" keyPath="unicode" id="Jvl-4K-DRa">
<binding destination="-2" name="hidden" keyPath="codePoint" id="efx-De-iSm">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
<binding destination="-2" name="value" keyPath="unicodeName" id="3kK-Ug-bH8">
<dictionary key="options">
<string key="NSNullPlaceholder">Invalid code</string>
</dictionary>
</binding>
</connections>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iYt-Yd-KXc">
@ -85,7 +85,7 @@ DQ
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="character.string" id="HAg-qi-yxV">
<binding destination="-2" name="value" keyPath="characterString" id="PG0-CO-irt">
<dictionary key="options">
<string key="NSNullPlaceholder">⬚</string>
</dictionary>

View File

@ -284,6 +284,12 @@
2A712CA218EFB716001FEC33 /* AutoTabExpand_Off.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 2A712CA018EFB716001FEC33 /* AutoTabExpand_Off.tiff */; };
2A712CA318EFB716001FEC33 /* AutoTabExpand_On.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 2A712CA118EFB716001FEC33 /* AutoTabExpand_On.tiff */; };
2A7135831CFFDC6600ADA555 /* FormattersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A7135821CFFDC6600ADA555 /* FormattersTests.swift */; };
2A73B5B41D4675350025337F /* UnicodeCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5B21D4675350025337F /* UnicodeCharacter.swift */; };
2A73B5B51D4675350025337F /* UnicodeCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5B21D4675350025337F /* UnicodeCharacter.swift */; };
2A73B5B61D4675350025337F /* UnicodeScalar+ControlCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5B31D4675350025337F /* UnicodeScalar+ControlCharacter.swift */; };
2A73B5B71D4675350025337F /* UnicodeScalar+ControlCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5B31D4675350025337F /* UnicodeScalar+ControlCharacter.swift */; };
2A73B5BC1D468DD30025337F /* UnicodeScalar+Information.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5BB1D468DD30025337F /* UnicodeScalar+Information.swift */; };
2A73B5BD1D468DD30025337F /* UnicodeScalar+Information.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A73B5BB1D468DD30025337F /* UnicodeScalar+Information.swift */; };
2A75ACCB19E86DDB00444894 /* CotEditor.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 2A75ACCA19E86DDB00444894 /* CotEditor.sdef */; };
2A78BF9F1D1AF82A00A583D2 /* IntegrationPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A78BF9E1D1AF82A00A583D2 /* IntegrationPaneController.swift */; };
2A78BFA01D1AF82A00A583D2 /* IntegrationPaneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A78BF9E1D1AF82A00A583D2 /* IntegrationPaneController.swift */; };
@ -353,8 +359,6 @@
2AA2E0101BFDE0190087BDD6 /* CharacterInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA2E00F1BFDE0190087BDD6 /* CharacterInfoTests.swift */; };
2AA2E0131BFE12620087BDD6 /* Unicode.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2AA2E0111BFE12620087BDD6 /* Unicode.strings */; };
2AA2E0141BFE12620087BDD6 /* Unicode.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2AA2E0111BFE12620087BDD6 /* Unicode.strings */; };
2AA2E01A1BFF9AEE0087BDD6 /* CEUnicodeCharacter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA2E0191BFF9AEE0087BDD6 /* CEUnicodeCharacter.m */; };
2AA2E01B1BFF9AEE0087BDD6 /* CEUnicodeCharacter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AA2E0191BFF9AEE0087BDD6 /* CEUnicodeCharacter.m */; };
2AA2E0261C0454730087BDD6 /* StringIndentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA2E0251C0454730087BDD6 /* StringIndentationTests.swift */; };
2AA375441D403F100080C27C /* String+Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4D69261D3FF61C00FBBD0B /* String+Encoding.swift */; };
2AA375451D403F110080C27C /* String+Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A4D69261D3FF61C00FBBD0B /* String+Encoding.swift */; };
@ -447,8 +451,6 @@
2AD69B861D3E42F700FBD998 /* TextSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD69B841D3E42F700FBD998 /* TextSelection.swift */; };
2AD69B881D3E4FCD00FBD998 /* EditorWrapper+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD69B871D3E4FCD00FBD998 /* EditorWrapper+Editor.swift */; };
2AD69B891D3E4FCD00FBD998 /* EditorWrapper+Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD69B871D3E4FCD00FBD998 /* EditorWrapper+Editor.swift */; };
2AD789D51C31AD81008B746F /* CEControlCharacerNames.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD789D41C31AD81008B746F /* CEControlCharacerNames.m */; };
2AD789D61C31AD81008B746F /* CEControlCharacerNames.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD789D41C31AD81008B746F /* CEControlCharacerNames.m */; };
2AD7B9AF1D3E832E00E5D6D7 /* DocumentAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7B9AE1D3E832E00E5D6D7 /* DocumentAnalyzer.swift */; };
2AD7B9B01D3E832E00E5D6D7 /* DocumentAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7B9AE1D3E832E00E5D6D7 /* DocumentAnalyzer.swift */; };
2AD7B9B51D3E9BD100E5D6D7 /* EditorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD7B9B41D3E9BD100E5D6D7 /* EditorWrapper.swift */; };
@ -728,6 +730,9 @@
2A712CA018EFB716001FEC33 /* AutoTabExpand_Off.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = AutoTabExpand_Off.tiff; sourceTree = "<group>"; };
2A712CA118EFB716001FEC33 /* AutoTabExpand_On.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = AutoTabExpand_On.tiff; sourceTree = "<group>"; };
2A7135821CFFDC6600ADA555 /* FormattersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormattersTests.swift; sourceTree = "<group>"; };
2A73B5B21D4675350025337F /* UnicodeCharacter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnicodeCharacter.swift; sourceTree = "<group>"; };
2A73B5B31D4675350025337F /* UnicodeScalar+ControlCharacter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnicodeScalar+ControlCharacter.swift"; sourceTree = "<group>"; };
2A73B5BB1D468DD30025337F /* UnicodeScalar+Information.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnicodeScalar+Information.swift"; sourceTree = "<group>"; };
2A75ACCA19E86DDB00444894 /* CotEditor.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = CotEditor.sdef; sourceTree = "<group>"; };
2A7846DA18FE035E006BDF00 /* Themes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Themes; sourceTree = "<group>"; };
2A78BF9E1D1AF82A00A583D2 /* IntegrationPaneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntegrationPaneController.swift; sourceTree = "<group>"; };
@ -804,8 +809,6 @@
2AA2E0121BFE12620087BDD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Unicode.strings; sourceTree = "<group>"; };
2AA2E0151BFE14310087BDD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Unicode.strings"; sourceTree = "<group>"; };
2AA2E0161BFE14320087BDD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Unicode.strings; sourceTree = "<group>"; };
2AA2E0181BFF9AEE0087BDD6 /* CEUnicodeCharacter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CEUnicodeCharacter.h; sourceTree = "<group>"; };
2AA2E0191BFF9AEE0087BDD6 /* CEUnicodeCharacter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CEUnicodeCharacter.m; sourceTree = "<group>"; };
2AA2E0251C0454730087BDD6 /* StringIndentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringIndentationTests.swift; sourceTree = "<group>"; };
2AA375461D40BDCB0080C27C /* LineEnding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineEnding.swift; sourceTree = "<group>"; };
2AA45A4A1D2E871900A1A401 /* EditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorViewController.swift; sourceTree = "<group>"; };
@ -932,8 +935,6 @@
2AD616CE1D3E67160016EFB6 /* ODBEditorSuite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ODBEditorSuite.swift; sourceTree = "<group>"; };
2AD69B841D3E42F700FBD998 /* TextSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextSelection.swift; sourceTree = "<group>"; };
2AD69B871D3E4FCD00FBD998 /* EditorWrapper+Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EditorWrapper+Editor.swift"; sourceTree = "<group>"; };
2AD789D31C31AD81008B746F /* CEControlCharacerNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CEControlCharacerNames.h; sourceTree = "<group>"; };
2AD789D41C31AD81008B746F /* CEControlCharacerNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CEControlCharacerNames.m; sourceTree = "<group>"; };
2AD7B9AE1D3E832E00E5D6D7 /* DocumentAnalyzer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentAnalyzer.swift; sourceTree = "<group>"; };
2AD7B9B41D3E9BD100E5D6D7 /* EditorWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorWrapper.swift; sourceTree = "<group>"; };
2AD9039A1BCE86A3004F2B8A /* SpellCheck.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = SpellCheck.tiff; sourceTree = "<group>"; };
@ -1565,10 +1566,9 @@
isa = PBXGroup;
children = (
2AF073FA1D34587500770BA6 /* CharacterInfo.swift */,
2AA2E0181BFF9AEE0087BDD6 /* CEUnicodeCharacter.h */,
2AA2E0191BFF9AEE0087BDD6 /* CEUnicodeCharacter.m */,
2AD789D31C31AD81008B746F /* CEControlCharacerNames.h */,
2AD789D41C31AD81008B746F /* CEControlCharacerNames.m */,
2A73B5B21D4675350025337F /* UnicodeCharacter.swift */,
2A73B5BB1D468DD30025337F /* UnicodeScalar+Information.swift */,
2A73B5B31D4675350025337F /* UnicodeScalar+ControlCharacter.swift */,
);
name = Character;
sourceTree = "<group>";
@ -2285,6 +2285,7 @@
2A4CCBB51D45173000294067 /* EditorTextView+LineProcessing.swift in Sources */,
2AF073E71D33CB6F00770BA6 /* ThemeDictionary.swift in Sources */,
2AD69B861D3E42F700FBD998 /* TextSelection.swift in Sources */,
2A73B5B51D4675350025337F /* UnicodeCharacter.swift in Sources */,
2A4257B11D22FD490086DAAD /* ColorCodePanelController.swift in Sources */,
2A5D13331D1FB90300D38E6A /* FindPanelTextView.swift in Sources */,
2AA45A521D2E938500A1A401 /* NavigationBarController.swift in Sources */,
@ -2298,6 +2299,7 @@
2A5D13141D1EE8FF00D38E6A /* HUDController.swift in Sources */,
2AA7613B1D457BD500031AAF /* String+Indentation.swift in Sources */,
2A78BFAB1D1B0E8B00A583D2 /* PreferencesWindowController.swift in Sources */,
2A73B5BD1D468DD30025337F /* UnicodeScalar+Information.swift in Sources */,
2AF073F51D34076A00770BA6 /* CharacterPopoverController.swift in Sources */,
2A8C33901D3E1C040005B0B7 /* IncompatibleCharacter.swift in Sources */,
2ACDC08C1D171F1E009B72D6 /* BackgroundView.swift in Sources */,
@ -2344,6 +2346,7 @@
2A38FAFE1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A4257BD1D239F850086DAAD /* Invisible.swift in Sources */,
2A2792961D1DBDAC00F3FC5D /* Constants.swift in Sources */,
2A73B5B71D4675350025337F /* UnicodeScalar+ControlCharacter.swift in Sources */,
2AAD61F11D2B0856008FE772 /* String+Range.swift in Sources */,
2AF073EC1D33F17100770BA6 /* DictionaryKey.swift in Sources */,
2A5D13231D1F9B7F00D38E6A /* PopUpToolbarItem.swift in Sources */,
@ -2429,8 +2432,6 @@
2AD49C291D0BEF3800FF4CC9 /* CESettingFileManager.m in Sources */,
2A6FD9FA1D3B05F100A59784 /* CESyntaxManager.m in Sources */,
2AB340801D01E2A4000F5EEC /* CESyntaxDictionaryKeys.m in Sources */,
2AA2E01B1BFF9AEE0087BDD6 /* CEUnicodeCharacter.m in Sources */,
2AD789D61C31AD81008B746F /* CEControlCharacerNames.m in Sources */,
2A41CB411C39167500F9122B /* CEDefaults.m in Sources */,
2A41CB471C391B7400F9122B /* CEErrors.m in Sources */,
2A6F0DA51B5500E100C2D03C /* Constants.m in Sources */,
@ -2506,6 +2507,7 @@
2A6FD9EA1D3A819500A59784 /* EditorTextView+Commenting.swift in Sources */,
2A78BFB61D1B2BA600A583D2 /* MigrationWindowController.swift in Sources */,
2A33D07E1D1C75B8005977B9 /* SyntaxValidationViewController.swift in Sources */,
2A73B5B61D4675350025337F /* UnicodeScalar+ControlCharacter.swift in Sources */,
2A6FD9E01D393F9100A59784 /* SplitViewController.swift in Sources */,
2A38FAFD1D1C67050032231A /* DraggableArrayController.swift in Sources */,
2A6FD9D11D38933100A59784 /* EditorTextViewController.swift in Sources */,
@ -2587,6 +2589,7 @@
2A5D13251D1F9D4100D38E6A /* TogglableToolbarItem.swift in Sources */,
2AA4D3741D1AA0AC001D261D /* KeyBindingsViewController.swift in Sources */,
2AACB1CD1D195ABD0073775B /* ShortcutKeyField.swift in Sources */,
2A73B5B41D4675350025337F /* UnicodeCharacter.swift in Sources */,
2A6FD9ED1D3A85D700A59784 /* String+NSRange.swift in Sources */,
2ACFE58B1D20730B0005233A /* FindPanelContentViewController.swift in Sources */,
2A78BFA41D1B02ED00A583D2 /* WindowPaneController.swift in Sources */,
@ -2598,6 +2601,7 @@
2A5D13321D1FB90300D38E6A /* FindPanelTextView.swift in Sources */,
2A38FB001D1C6B6D0032231A /* SyntaxEditTableViewDelegate.swift in Sources */,
2A5D13351D1FC87900D38E6A /* FindPanelTextClipView.swift in Sources */,
2A73B5BC1D468DD30025337F /* UnicodeScalar+Information.swift in Sources */,
2AFAFD4A1D41487600F1458F /* PrintTextView.swift in Sources */,
2A5D130A1D1ED10400D38E6A /* ConsolePanelController.swift in Sources */,
2AA45A511D2E938500A1A401 /* NavigationBarController.swift in Sources */,
@ -2614,8 +2618,6 @@
2AD49C281D0BEF3800FF4CC9 /* CESettingFileManager.m in Sources */,
2A6FD9F91D3B05F100A59784 /* CESyntaxManager.m in Sources */,
2AB3407F1D01E2A4000F5EEC /* CESyntaxDictionaryKeys.m in Sources */,
2AA2E01A1BFF9AEE0087BDD6 /* CEUnicodeCharacter.m in Sources */,
2AD789D51C31AD81008B746F /* CEControlCharacerNames.m in Sources */,
2A41CB401C39167500F9122B /* CEDefaults.m in Sources */,
2A41CB461C391B7400F9122B /* CEErrors.m in Sources */,
2A149DD719033CBB00A9D6EF /* Constants.m in Sources */,

View File

@ -1,36 +0,0 @@
/*
CEControlCharacerNames.h
CotEditor
http://coteditor.com
Created by 1024jp on 2015-12-29.
------------------------------------------------------------------------------
© 2015 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@import Foundation;
BOOL CEIsC0ControlPoint(unichar character);
BOOL CEIsC1ControlPoint(unichar character);
extern unichar const CEDeleteCharacter;
NSString * _Nullable CEControlCharacterName(unichar character);

View File

@ -1,136 +0,0 @@
/*
CEControlCharacerNames.m
CotEditor
http://coteditor.com
Created by 1024jp on 2015-12-29.
------------------------------------------------------------------------------
© 2015 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "CEControlCharacerNames.h"
BOOL CEIsC0ControlPoint(unichar character) {
return 0x0000 <= character && character <= 0x0020; // U+0020 is actually not in range of C0 control character. But they are often included in actual fact.
}
BOOL CEIsC1ControlPoint(unichar character) {
return 0x0080 <= character && character <= 0x009F;
}
unichar const CEDeleteCharacter = 0x007F;
// c.f. http://unicode.org/Public/UNIDATA/UnicodeData.txt
static NSString *_Nonnull const CEC0ControlCharacterNames[] = {
@"NULL",
@"START OF HEADING",
@"START OF TEXT",
@"END OF TEXT",
@"END OF TRANSMISSION",
@"ENQUIRY",
@"ACKNOWLEDGE",
@"BELL",
@"BACKSPACE",
@"HORIZONTAL TABULATION",
@"LINE FEED",
@"VERTICAL TABULATION",
@"FORM FEED",
@"CARRIAGE RETURN",
@"SHIFT OUT",
@"SHIFT IN",
@"DATA LINK ESCAPE",
@"DEVICE CONTROL ONE",
@"DEVICE CONTROL TWO",
@"DEVICE CONTROL THREE",
@"DEVICE CONTROL FOUR",
@"NEGATIVE ACKNOWLEDGE",
@"SYNCHRONOUS IDLE",
@"END OF TRANSMISSION BLOCK",
@"CANCEL",
@"END OF MEDIUM",
@"SUBSTITUTE",
@"ESCAPE",
@"FILE SEPARATOR",
@"GROUP SEPARATOR",
@"RECORD SEPARATOR",
@"UNIT SEPARATOR",
@"SPACE",
};
static NSString *_Nonnull const CEDeleteCharacterName = @"DELETE";
static NSString *_Nonnull const CEC1ControlCharacterNames[] = {
@"PADDING CHARACTER",
@"HIGH OCTET PRESET",
@"BREAK PERMITTED HERE",
@"NO BREAK HERE",
@"INDEX",
@"NEXT LINE",
@"START OF SELECTED AREA",
@"END OF SELECTED AREA",
@"CHARACTER TABULATION SET",
@"CHARACTER TABULATION WITH JUSTIFICATION",
@"LINE TABULATION SET",
@"PARTIAL LINE FORWARD",
@"PARTIAL LINE BACKWARD",
@"REVERSE LINE FEED",
@"SINGLE SHIFT TWO",
@"SINGLE SHIFT THREE",
@"DEVICE CONTROL STRING",
@"PRIVATE USE ONE",
@"PRIVATE USE TWO",
@"SET TRANSMIT STATE",
@"CANCEL CHARACTER",
@"MESSAGE WAITING",
@"START OF PROTECTED AREA",
@"END OF PROTECTED AREA",
@"START OF STRING",
@"SINGLE GRAPHIC CHARACTER INTRODUCER",
@"SINGLE CHARACTER INTRODUCER",
@"CONTROL SEQUENCE INTRODUCER",
@"STRING TERMINATOR",
@"OPERATING SYSTEM COMMAND",
@"PRIVACY MESSAGE",
@"APPLICATION PROGRAM COMMAND",
};
NSString * _Nullable CEControlCharacterName(unichar character)
{
if (CEIsC0ControlPoint(character)) {
NSUInteger index = character;
return CEC0ControlCharacterNames[index];
} else if (character == CEDeleteCharacter) {
return CEDeleteCharacterName;
} else if (CEIsC1ControlPoint(character)) {
NSUInteger index = character - 0x0080; // shift to 0-based array index
return CEC1ControlCharacterNames[index];
}
return nil;
}

View File

@ -1,51 +0,0 @@
/*
CEUnicodeCharacter.h
CotEditor
http://coteditor.com
Created by 1024jp on 2015-11-21.
------------------------------------------------------------------------------
© 2015 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@import Foundation;
@interface CEUnicodeCharacter : NSObject
@property (nonatomic, readonly) UTF32Char character;
@property (nonatomic, readonly) unichar pictureCharacter;
@property (nonatomic, readonly, nonnull, copy) NSString *unicode;
@property (nonatomic, readonly, nonnull, copy) NSString *string;
@property (nonatomic, readonly, getter=isSurrogatePair) BOOL surrogatePair;
@property (nonatomic, readonly, nullable, copy) NSArray<NSString *> *surrogateUnicodes;
@property (nonatomic, readonly, nonnull, copy) NSString *name;
@property (nonatomic, readonly, nonnull, copy) NSString *categoryName;
@property (nonatomic, readonly, nonnull, copy) NSString *blockName;
@property (nonatomic, readonly, nonnull, copy) NSString *localizedBlockName;
+ (nonnull CEUnicodeCharacter *)unicodeCharacterWithCharacter:(UTF32Char)character;
/// designated initializer
- (nonnull instancetype)initWithCharacter:(UTF32Char)character NS_DESIGNATED_INITIALIZER;
- (nonnull instancetype)init NS_UNAVAILABLE;
@end

View File

@ -1,243 +0,0 @@
/*
CEUnicodeCharacter.m
CotEditor
http://coteditor.com
Created by 1024jp on 2015-11-21.
------------------------------------------------------------------------------
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@import ICU;
#import "CEUnicodeCharacter.h"
#import "CEControlCharacerNames.h"
@interface CEUnicodeCharacter ()
// readonly
@property (nonatomic, readwrite) UTF32Char character;
@property (nonatomic, readwrite) unichar pictureCharacter;
@property (nonatomic, readwrite, nonnull, copy) NSString *string;
@property (nonatomic, readwrite, nonnull, copy) NSString *unicode;
@property (nonatomic, readwrite, getter=isSurrogatePair) BOOL surrogatePair;
@property (nonatomic, readwrite, nullable, copy) NSArray<NSString *> *surrogateUnicodes;
@property (nonatomic, readwrite, nonnull, copy) NSString *name;
@property (nonatomic, readwrite, nonnull, copy) NSString *categoryName;
@property (nonatomic, readwrite, nonnull, copy) NSString *blockName;
@property (nonatomic, readwrite, nonnull, copy) NSString *localizedBlockName;
@end
@implementation CEUnicodeCharacter
//------------------------------------------------------
/// disable superclass's designated initializer
- (nonnull instancetype)init
//------------------------------------------------------
{
@throw nil;
}
#pragma mark Public Methods
//------------------------------------------------------
/// convenience constractor
+ (nonnull CEUnicodeCharacter *)unicodeCharacterWithCharacter:(UTF32Char)character
//------------------------------------------------------
{
return [[CEUnicodeCharacter alloc] initWithCharacter:character];
}
//------------------------------------------------------
/// designated initializer
- (nonnull instancetype)initWithCharacter:(UTF32Char)character
//------------------------------------------------------
{
self = [super init];
if (self) {
_character = character;
_unicode = [NSString stringWithFormat:@"U+%04tX", character];
// surrogate pair check
UniChar surrogates[2];
_surrogatePair = CFStringGetSurrogatePairForLongCharacter(character, surrogates);
if (_surrogatePair) {
_surrogateUnicodes = @[[NSString stringWithFormat:@"U+%04X", surrogates[0]],
[NSString stringWithFormat:@"U+%04X", surrogates[1]]];
}
// UTF32Char to NSString
BOOL isSingleSurrogate = CFStringIsSurrogateHighCharacter(character) || CFStringIsSurrogateLowCharacter(character);
if (isSingleSurrogate) {
unichar singleChar = character; // downcast
_string = [[NSString alloc] initWithCharacters:&singleChar length:1];
} else {
UTF32Char littleEdian = NSSwapHostIntToLittle(character);
_string = [[NSString alloc] initWithBytes:&littleEdian length:4 encoding:NSUTF32LittleEndianStringEncoding];
}
NSAssert(_string != nil, @"Failed to covnert UTF32Char U+%04tX into NSString.", character);
// alternate picture caracter for invisible control character
if (CEIsC0ControlPoint(_character)) {
_pictureCharacter = _character + 0x2400; // shift 0x2400 to Unicode control pictures
} else if (_character == CEDeleteCharacter) { // DELETE character
_pictureCharacter = 0x2421; // SYMBOL FOR DELETE character
}
}
return self;
}
# pragma mark Public Accessors
// ------------------------------------------------------
/// Unicode name
- (nonnull NSString *)name
// ------------------------------------------------------
{
// defer init
if (!_name) {
_name = CEControlCharacterName([self character]);
if (!_name) {
NSMutableString *unicodeName = [[self string] mutableCopy];
// You can't use kCFStringTransformToUnicodeName instead of `Any-Name` here,
// because some characters (e.g. normal `a`) don't return their name when use this constant.
CFStringTransform((__bridge CFMutableStringRef)unicodeName, NULL, CFSTR("Any-Name"), NO);
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\{(.+?)\\}" options:0 error:nil];
NSTextCheckingResult *firstMatch = [regex firstMatchInString:unicodeName options:0
range:NSMakeRange(0, [unicodeName length])];
_name = [unicodeName substringWithRange:[firstMatch rangeAtIndex:1]];
}
}
return _name;
}
// ------------------------------------------------------
/// Unicode category name
- (nonnull NSString *)categoryName
// ------------------------------------------------------
{
// defer init
if (!_categoryName) {
int32_t prop = u_getIntPropertyValue([self character], UCHAR_GENERAL_CATEGORY);
const char *categoryNameChars = u_getPropertyValueName(UCHAR_GENERAL_CATEGORY, prop, U_LONG_PROPERTY_NAME);
_categoryName = [@(categoryNameChars) stringByReplacingOccurrencesOfString:@"_" withString:@" "]; // sanitize
}
return _categoryName;
}
// ------------------------------------------------------
/// Unicode block name just returned from an icu function
- (nonnull NSString *)blockName
// ------------------------------------------------------
{
// defer init
if (!_blockName) {
int32_t prop = u_getIntPropertyValue([self character], UCHAR_BLOCK);
const char *blockNameChars = u_getPropertyValueName(UCHAR_BLOCK, prop, U_LONG_PROPERTY_NAME);
_blockName = [@(blockNameChars) stringByReplacingOccurrencesOfString:@"_" withString:@" "]; // sanitize
}
return _blockName;
}
// ------------------------------------------------------
/// Localized and sanitized unicode block name
- (nonnull NSString *)localizedBlockName
// ------------------------------------------------------
{
// defer init
if (!_localizedBlockName) {
NSString *blockName = [self blockName];
blockName = [[self class] sanitizeBlockName:blockName];
_localizedBlockName = NSLocalizedStringFromTable(blockName, @"Unicode", nil);
}
return _localizedBlockName;
}
#pragma mark Private Methods
// ------------------------------------------------------
/// sanitize block name for localization
+ (nonnull NSString *)sanitizeBlockName:(nonnull NSString *)blockName
// ------------------------------------------------------
{
// -> This is actually a dirty workaround to make the block name the same as the Apple's block naming rule.
// Otherwise, we cannot localize block name correctly. (2015-11 by 1024jp)
blockName = [blockName stringByReplacingOccurrencesOfString:@" ([A-Z])$" withString:@"-$1"
options:NSRegularExpressionSearch range:NSMakeRange(0, [blockName length])];
blockName = [blockName stringByReplacingOccurrencesOfString:@"Extension-" withString:@"Ext. "];
blockName = [blockName stringByReplacingOccurrencesOfString:@" And " withString:@" and "];
blockName = [blockName stringByReplacingOccurrencesOfString:@" For " withString:@" for "];
blockName = [blockName stringByReplacingOccurrencesOfString:@" Mathematical " withString:@" Math "];
blockName = [blockName stringByReplacingOccurrencesOfString:@"Latin 1" withString:@"Latin-1"]; // only for "Latin-1
return blockName;
}
// ------------------------------------------------------
/// check which block names will be lozalized (only for test use)
+ (void)testUnicodeBlockNameLocalizationForLanguage:(NSString *)language
// ------------------------------------------------------
{
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:language withExtension:@"lproj"];
NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
for (int i = 0; i < UBLOCK_COUNT; i++) {
const char *blockNameChars = u_getPropertyValueName(UCHAR_BLOCK, i, U_LONG_PROPERTY_NAME);
NSString *blockName = [@(blockNameChars) stringByReplacingOccurrencesOfString:@"_" withString:@" "]; // sanitize
blockName = [[self class] sanitizeBlockName:blockName];
NSString *localizedBlockName = [bundle localizedStringForKey:blockName value:nil table:@"Unicode"];
NSLog(@"%@ %@ %@", [localizedBlockName isEqualToString:blockName] ? @"⚠️" : @" ", blockName, localizedBlockName);
}
}
@end

View File

@ -43,6 +43,7 @@ extension UnicodeScalar {
static let textSequence = UnicodeScalar(0xFE0E)
static let emojiSequence = UnicodeScalar(0xFE0F)
var isVariantSelector: Bool {
let codePoint = self.value
@ -56,13 +57,13 @@ extension UnicodeScalar {
// MARK:
class CharacterInfo: CustomDebugStringConvertible { // TODO: struct?
class CharacterInfo: CustomStringConvertible, CustomDebugStringConvertible { // TODO: struct?
// MARK: Public Properties
let string: String
let pictureString: String?
let unicodes: [CEUnicodeCharacter]
let unicodes: [UnicodeCharacter]
let isComplex: Bool
@ -80,58 +81,59 @@ class CharacterInfo: CustomDebugStringConvertible { // TODO: struct?
guard string.numberOfComposedCharacters == 1 || string == "\r\n" else { return nil }
// -> Number of String.characters.count and numberOfComposedCharacters are different.
let unicodes = string.unicodes
self.string = string
self.unicodes = string.unicodes
self.unicodes = unicodes
// check variation selector
var additional: String?
var isComplex = false
if self.unicodes.count == 2 {
let scalar = UnicodeScalar(unicodes.last!.character)
let additional: String? = {
guard unicodes.count == 2, let lastUnicode = unicodes.last?.character else { return nil }
switch (scalar) {
switch lastUnicode {
case UnicodeScalar.emojiSequence:
additional = "Emoji Style"
return "Emoji Style"
case UnicodeScalar.textSequence:
additional = "Text Style"
return "Text Style"
case SkinToneEmojiModifier.type12:
additional = "Skin Tone I-II"
return "Skin Tone I-II"
case SkinToneEmojiModifier.type3:
additional = "Skin Tone III"
return "Skin Tone III"
case SkinToneEmojiModifier.type4:
additional = "Skin Tone IV"
return "Skin Tone IV"
case SkinToneEmojiModifier.type5:
additional = "Skin Tone V"
return "Skin Tone V"
case SkinToneEmojiModifier.type6:
additional = "Skin Tone VI"
return "Skin Tone VI"
case let unicode where unicode.isVariantSelector:
return "Variant"
default:
if scalar.isVariantSelector {
additional = "Variant"
} else {
isComplex = true
}
return nil
}
} else if self.unicodes.count > 2 {
isComplex = true
}
}()
self.variationSelectorAdditional = additional
self.isComplex = isComplex
self.isComplex = (unicodes.count > 1 && additional == nil)
var pictureString: String?
if self.unicodes.count == 1 { // ignore CR/LF
if let pictureCharacter = self.unicodes.first?.pictureCharacter, pictureCharacter != 0 {
let scalar = UnicodeScalar(pictureCharacter)
pictureString = String(scalar)
}
}
self.pictureString = pictureString
self.pictureString = {
guard unicodes.count == 1, // ignore CR/LF
let pictureCharacter = unicodes.first?.pictureCharacter else { return nil }
return String(Character(pictureCharacter))
}()
}
var description: String {
return "\(self.string)"
}
@ -168,9 +170,9 @@ class CharacterInfo: CustomDebugStringConvertible { // TODO: struct?
private extension String {
/// devide given string into UnicodeCharacter objects
var unicodes: [CEUnicodeCharacter] {
var unicodes: [UnicodeCharacter] {
return self.unicodeScalars.map { CEUnicodeCharacter(character: $0.value) }
return self.unicodeScalars.map { UnicodeCharacter(character: $0) }
}
}

View File

@ -12,8 +12,6 @@
#import "CESyntaxManager.h"
#import "CESyntaxDictionaryKeys.h"
#import "CEUnicodeCharacter.h"
// Document
#import "CEPrintPanelAccessoryController.h"
#import "NSTextView+CETextReplacement.h"

View File

@ -244,11 +244,10 @@ class DocumentAnalyzer: NSObject {
// unicode
if needsAll && hasSelection {
if let unicodes = CharacterInfo(string: selectedString)?.unicodes,
let first = unicodes.first,
unicodes.count == 1
if selectedString.unicodeScalars.count == 1,
let first = selectedString.unicodeScalars.first
{
unicode = first.unicode
unicode = first.codePoint
}
}
}

View File

@ -0,0 +1,159 @@
/*
UnicodeCharacter.swift
CotEditor
https://coteditor.com
Created by 1024jp on 2015-11-21.
------------------------------------------------------------------------------
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import ICU
class UnicodeCharacter: CustomStringConvertible, CustomDebugStringConvertible {
// MARK: Public Properties
let character: UnicodeScalar
/// Unicode name
private(set) lazy var name: String = self.character.name ?? ""
/// alternate picture caracter for invisible control character
private(set) lazy var pictureCharacter: UnicodeScalar? = self.character.pictureRepresentation
let unicode: String
let string: String
let isSurrogatePair: Bool
let surrogateUnicodes: [String]?
// MARK:
// MARK: Lifecycle
required init(character: UnicodeScalar) {
self.character = character
self.unicode = character.codePoint
// surrogate pair check
self.isSurrogatePair = character.isSurrogatePair
if self.isSurrogatePair {
self.surrogateUnicodes = [String(format: "U+%04X", UTF16.leadSurrogate(character)),
String(format: "U+%04X", UTF16.trailSurrogate(character))]
} else {
self.surrogateUnicodes = nil
}
// UnicodeScalar to String
self.string = String(Character(character))
}
var description: String {
return "\(self.character)"
}
var debugDescription: String {
return "<\(self): \(self.character)>"
}
// MARK: Lazy Properties
/// Unicode category name
private(set) lazy var categoryName: String = {
guard let name = self.character.categoryName else {
return NSLocalizedString("Unknown category", tableName: "Unicode", comment: "")
}
return name
}()
/// Unicode block name just returned from an icu function
private(set) lazy var blockName: String = {
guard let name = self.character.blockName else {
return NSLocalizedString("Unknown block", tableName: "Unicode", comment: "")
}
return name
}()
/// Localized and sanitized unicode block name
private(set) lazy var localizedBlockName: String = {
let blockName = sanitize(blockName: self.blockName)
return NSLocalizedString(sanitize(blockName: self.blockName), tableName: "Unicode", comment: "")
}()
}
/// sanitize block name for localization
private func sanitize(blockName: String) -> String
{
// -> This is actually a dirty workaround to make the block name the same as the Apple's block naming rule.
// Otherwise, we cannot localize block name correctly. (2015-11 by 1024jp)
var sanitized = blockName
sanitized = sanitized.replacingOccurrences(of: " ([A-Z])$", with: "-$1", options: .regularExpression)
sanitized = sanitized.replacingOccurrences(of: "Extension-", with: "Ext. ")
sanitized = sanitized.replacingOccurrences(of: " And ", with: " and ")
sanitized = sanitized.replacingOccurrences(of: " For ", with: " for ")
sanitized = sanitized.replacingOccurrences(of: " Mathematical ", with: " Math ")
sanitized = sanitized.replacingOccurrences(of: "Latin 1", with: "Latin-1") // only for "Latin-1
return sanitized
}
// MARK: - Test
/// check which block names will be lozalized (only for test use)
private func testUnicodeBlockNameLocalization(for language: String) {
let bundleURL = Bundle.main.urlForResource(language, withExtension: "lproj")!
let bundle = Bundle(url: bundleURL)
for index in 0..<UBLOCK_COUNT.rawValue {
let blockNameChars = u_getPropertyValueName(UCHAR_BLOCK, index, U_LONG_PROPERTY_NAME)
var blockName = String(blockNameChars).replacingOccurrences(of: "_", with: " ") // sanitize
blockName = sanitize(blockName: blockName)
let localizedBlockName = bundle?.localizedString(forKey: blockName, value: nil, table: "Unicode")
print((localizedBlockName == blockName) ? "⚠️" : " ", blockName, localizedBlockName)
}
}

View File

@ -42,20 +42,14 @@ class UnicodeInputPanelController: NSWindowController, NSTextFieldDelegate {
static let shared = UnicodeInputPanelController()
dynamic var characterString: String? {
return self.character?.string
}
private(set) dynamic var characterString: String?
// MARK: Private Properties
private dynamic var unicode = ""
private dynamic var codePoint: String?
private dynamic var isValid = false
private dynamic var character: CEUnicodeCharacter?
private let unicodeRegex = try! RegularExpression(pattern: "^(?:U\\+|0x|\\\\u)?([0-9a-f]{1,5})$", options: .caseInsensitive)
private dynamic var unicodeName: String?
@ -104,12 +98,26 @@ class UnicodeInputPanelController: NSWindowController, NSTextFieldDelegate {
/// text in text field was changed
override func controlTextDidChange(_ obj: Notification) {
guard let input = (obj.object as? NSTextField)?.stringValue else { return }
var isValid = false
var unicodeName: String? = nil
var characterString: String? = nil
let result = self.unicodeRegex.firstMatch(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
defer {
self.isValid = isValid
self.unicodeName = unicodeName
self.characterString = characterString
}
self.isValid = (result != nil)
self.character = self.isValid ? CEUnicodeCharacter(character: self.longChar) : nil
guard
let input = (obj.object as? NSTextField)?.stringValue,
let longChar = UInt32(codePoint: input) else { return }
unicodeName = longChar.unicodeName
guard let scalar = UnicodeScalar(codePoint: longChar) else { return }
isValid = true
characterString = String(Character(scalar))
}
@ -128,25 +136,28 @@ class UnicodeInputPanelController: NSWindowController, NSTextFieldDelegate {
receiver.insertUnicodeCharacter(self)
self.unicode = ""
self.character = nil
self.codePoint = ""
self.isValid = false
}
// MARK: Private Methods
/// UTF32Char form of current input unicode codepoint
private var longChar: UTF32Char {
let scanner = Scanner(string: self.unicode)
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "uU+\\")
var longChar: UTF32Char = 0
scanner.scanHexInt32(&longChar)
return longChar
self.unicodeName = nil
self.characterString = nil
}
}
// MARK: Private Methods
private extension UInt32 {
/// initialize from a possible Unicode code point representation like `U+1F600`, `1f600`, `0x1F600` and so on.
init?(codePoint: String) {
guard let range = codePoint.range(of: "(?<=^(U\\+|0x|\\\\u)?)[0-9a-f]{1,5}$",
options: [.regularExpression, .caseInsensitive]) else { return nil }
let hexString = codePoint.substring(with: range)
self.init(hexString, radix: 16)
}
}

View File

@ -0,0 +1,160 @@
/*
UnicodeScalar+ControlCharacter.swift
CotEditor
http://coteditor.com
Created by 1024jp on 2015-12-29.
------------------------------------------------------------------------------
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
extension UnicodeScalar {
/// alternate picture caracter for invisible control character
var pictureRepresentation: UnicodeScalar? {
if ControlCharacter.C0Range.contains(self.value) {
return UnicodeScalar(self.value + 0x2400) // shift 0x2400 to Unicode control pictures
}
if self.value == ControlCharacter.deleteCharacter { // DELETE character
return UnicodeScalar(0x2421) // SYMBOL FOR DELETE character
}
return nil
}
}
extension UTF32Char {
/// unicode name if receiver is a control character
var controlCharacterName: String? {
if ControlCharacter.C0Range.contains(self) {
let index = Int(self)
return ControlCharacter.C0Names[index]
}
if self == ControlCharacter.deleteCharacter {
return ControlCharacter.deleteName
}
if ControlCharacter.C1Range.contains(self) {
let index = Int(self - ControlCharacter.C1Range.lowerBound) // shift to 0-based array index
return ControlCharacter.C1Names[index]
}
return nil
}
}
// MARK: -
enum ControlCharacter {
// MARK: Code Point Ranges
static let deleteCharacter = UInt32(0x007F)
static let C0Range = UInt32(0x0000)...UInt32(0x0020) // U+0020 is actually not in range of C0 control character. But they are often included in actual fact.
static let C1Range = UInt32(0x0080)...UInt32(0x009F)
// MARK: Names
static let deleteName = "DELETE"
static let C0Names = [
"NULL",
"START OF HEADING",
"START OF TEXT",
"END OF TEXT",
"END OF TRANSMISSION",
"ENQUIRY",
"ACKNOWLEDGE",
"BELL",
"BACKSPACE",
"HORIZONTAL TABULATION",
"LINE FEED",
"VERTICAL TABULATION",
"FORM FEED",
"CARRIAGE RETURN",
"SHIFT OUT",
"SHIFT IN",
"DATA LINK ESCAPE",
"DEVICE CONTROL ONE",
"DEVICE CONTROL TWO",
"DEVICE CONTROL THREE",
"DEVICE CONTROL FOUR",
"NEGATIVE ACKNOWLEDGE",
"SYNCHRONOUS IDLE",
"END OF TRANSMISSION BLOCK",
"CANCEL",
"END OF MEDIUM",
"SUBSTITUTE",
"ESCAPE",
"FILE SEPARATOR",
"GROUP SEPARATOR",
"RECORD SEPARATOR",
"UNIT SEPARATOR",
"SPACE",
]
static let C1Names = [
"PADDING CHARACTER",
"HIGH OCTET PRESET",
"BREAK PERMITTED HERE",
"NO BREAK HERE",
"INDEX",
"NEXT LINE",
"START OF SELECTED AREA",
"END OF SELECTED AREA",
"CHARACTER TABULATION SET",
"CHARACTER TABULATION WITH JUSTIFICATION",
"LINE TABULATION SET",
"PARTIAL LINE FORWARD",
"PARTIAL LINE BACKWARD",
"REVERSE LINE FEED",
"SINGLE SHIFT TWO",
"SINGLE SHIFT THREE",
"DEVICE CONTROL STRING",
"PRIVATE USE ONE",
"PRIVATE USE TWO",
"SET TRANSMIT STATE",
"CANCEL CHARACTER",
"MESSAGE WAITING",
"START OF PROTECTED AREA",
"END OF PROTECTED AREA",
"START OF STRING",
"SINGLE GRAPHIC CHARACTER INTRODUCER",
"SINGLE CHARACTER INTRODUCER",
"CONTROL SEQUENCE INTRODUCER",
"STRING TERMINATOR",
"OPERATING SYSTEM COMMAND",
"PRIVACY MESSAGE",
"APPLICATION PROGRAM COMMAND",
]
}

View File

@ -0,0 +1,143 @@
/*
UnicodeScalar+Information.swift
CotEditor
https://coteditor.com
Created by 1024jp on 2016-07-26.
------------------------------------------------------------------------------
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import ICU
extension UnicodeScalar {
/// initialize only if code point is in valid range for a UnicodeScalar
init?(codePoint: UInt32) {
let surrogateRange = UInt32(0xD800)...UInt32(0xDFFF) // high- and low-surrogate code points are not valid Unicode scalar values
let codeSpaceEdge: UInt32 = 0x10FFFF // value is outside of Unicode codespace
guard !surrogateRange.contains(codePoint) && codePoint <= codeSpaceEdge else { return nil }
self.init(codePoint)
}
/// code point string in format like `U+000F`
var codePoint: String {
return String(format: "U+%04tX", self.value)
}
var isSurrogatePair: Bool {
return (UTF16.width(self) == 2)
}
/// Unicode name
var name: String? {
return UTF32Char(self.value).unicodeName
}
/// Unicode category name just returned from the ICU function
var categoryName: String? {
return UTF32Char(self.value).categoryName
}
/// Unicode block name just returned from the ICU function
var blockName: String? {
return UTF32Char(self.value).blockName
}
}
// MARK: -
// Implemented Unicode functions at UTF32Char level in order to cover single surrogate character that is not allowed by UnicodeScalar
extension UTF32Char {
/// get Unicode name
var unicodeName: String? {
if let name = self.controlCharacterName {
return name
}
return self.name(for: U_UNICODE_CHAR_NAME) ?? self.name(for: U_EXTENDED_CHAR_NAME)
// -> `U_UNICODE_CHAR_NAME` returns modern Unicode name however it doesn't support surrogate character names.
// `U_EXTENDED_CHAR_NAME` returns lowercase name within angle brackets like "<lead surrogate-D83D>".
// Therefore, we combinate `U_UNICODE_CHAR_NAME` and `U_EXTENDED_CHAR_NAME`.
}
/// Unicode category name just returned from the ICU function
var categoryName: String? {
return self.property(for: UCHAR_GENERAL_CATEGORY)
}
/// Unicode block name just returned from the ICU function
var blockName: String? {
return self.property(for: UCHAR_BLOCK)
}
// MARK: Private Methods
/// get character name with name type
private func name(for type: UCharNameChoice) -> String? {
var buffer = [CChar](repeating: 0, count: 128)
var error = U_ZERO_ERROR
u_charName(UChar32(self), type, &buffer, 128, &error)
guard error == U_ZERO_ERROR,
let name = String(utf8String: buffer), !name.isEmpty else { return nil }
return name
}
/// get Unicode property for property key
private func property(for property: UProperty) -> String? {
let prop = u_getIntPropertyValue(UChar32(self), property)
guard let name = u_getPropertyValueName(property, prop, U_LONG_PROPERTY_NAME) else { return nil }
return String(cString: name).replacingOccurrences(of: "_", with: " ")
}
}

View File

@ -9,7 +9,7 @@
------------------------------------------------------------------------------
© 2015 1024jp
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -254,3 +254,7 @@
"Gujarati" = "Gujarati";
"Tai Xuan Jing Symbols" = "Tai Xuan Jing Symbole";
"Kana Supplement" = "Ergänzungen zu Kana";
"Unknown block" = "Unknown block"; // FIXME: added
// Unicode category names
"Unknown category" = "Unknown category"; // FIXME: added

View File

@ -9,7 +9,7 @@
------------------------------------------------------------------------------
© 2015 1024jp
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -254,3 +254,7 @@
"Gujarati" = "グジャラート文字";
"Tai Xuan Jing Symbols" = "太玄経の記号";
"Kana Supplement" = "かな補助";
"Unknown block" = "不明なブロック";
// Unicode category names
"Unknown category" = "不明なカテゴリ";

View File

@ -9,7 +9,7 @@
------------------------------------------------------------------------------
© 2015 1024jp
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -254,3 +254,8 @@
"Gujarati" = "古吉拉特文";
"Tai Xuan Jing Symbols" = "太玄经符号";
"Kana Supplement" = "假名补充";
"Unknown block" = "Unknown block"; // FIXME: added
// Unicode category names
"Unknown category" = "Unknown category"; // FIXME: added

View File

@ -10,7 +10,7 @@
------------------------------------------------------------------------------
© 2015 1024jp
© 2015-2016 1024jp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -31,10 +31,26 @@ import XCTest
class CharacterInfoTests: XCTestCase {
// MARK: - CEUnicodeCharacter Tests
// MARK: UTF32Char Extension Tests
func testSingleSurrogate() {
let character = UTF32Char(0xD83D)
XCTAssertEqual(character.unicodeName, "<lead surrogate-D83D>")
XCTAssertEqual(character.categoryName, "Surrogate")
XCTAssertEqual(character.blockName, "High Surrogates")
XCTAssertNil(UnicodeScalar(codePoint: character))
}
// MARK: - UnicodeCharacter Tests
func testSingleChar() {
let character = CEUnicodeCharacter(character: UTF32Char(""))
let character = UnicodeCharacter(character: UnicodeScalar(""))
XCTAssertEqual(CChar32(character.character), CChar32(""))
XCTAssertEqual(character.unicode, "U+3042")
@ -48,18 +64,9 @@ class CharacterInfoTests: XCTestCase {
}
func testSingleSurrogate() {
let character = CEUnicodeCharacter(character: UTF32Char(0xD83D))
XCTAssertEqual(character.unicode, "U+D83D")
XCTAssertEqual(character.name, "<lead surrogate-D83D>")
XCTAssertEqual(character.categoryName, "Surrogate")
XCTAssertEqual(character.blockName, "High Surrogates")
}
func testSurrogateEmoji() {
let character = CEUnicodeCharacter(character: UTF32Char("😀"))
let character = UnicodeCharacter(character: UnicodeScalar("😀"))
XCTAssertEqual(CChar32(character.character), CChar32("😀"))
XCTAssertEqual(character.unicode, "U+1F600")
@ -74,7 +81,8 @@ class CharacterInfoTests: XCTestCase {
func testUnicodeBlockNameWithHyphen() {
let character = CEUnicodeCharacter(character: UTF32Char(""))
let character = UnicodeCharacter(character: UnicodeScalar(""))
XCTAssertEqual(character.unicode, "U+FDFD")
XCTAssertEqual(character.name, "ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM")
@ -83,43 +91,46 @@ class CharacterInfoTests: XCTestCase {
func testUnicodeControlPictures() {
// test NULL
let nullCharacter = CEUnicodeCharacter(character: UTF32Char(0x0000))
let nullPictureCharacter = CEUnicodeCharacter(character: UTF32Char(0x2400))
let nullCharacter = UnicodeCharacter(character: UnicodeScalar(0x0000))
let nullPictureCharacter = UnicodeCharacter(character: UnicodeScalar(0x2400))
XCTAssertEqual(nullCharacter.name, "NULL")
XCTAssertEqual(nullPictureCharacter.name, "SYMBOL FOR NULL")
XCTAssertEqual(nullCharacter.pictureCharacter, unichar(nullPictureCharacter.character))
XCTAssertEqual(nullCharacter.pictureCharacter, nullPictureCharacter.character)
// test SPACE
let spaceCharacter = CEUnicodeCharacter(character: UTF32Char(0x0020))
let spacePictureCharacter = CEUnicodeCharacter(character: UTF32Char(0x2420))
let spaceCharacter = UnicodeCharacter(character: UnicodeScalar(0x0020))
let spacePictureCharacter = UnicodeCharacter(character: UnicodeScalar(0x2420))
XCTAssertEqual(spaceCharacter.name, "SPACE")
XCTAssertEqual(spacePictureCharacter.name, "SYMBOL FOR SPACE")
XCTAssertEqual(spaceCharacter.pictureCharacter, unichar(spacePictureCharacter.character))
XCTAssertEqual(spaceCharacter.pictureCharacter, spacePictureCharacter.character)
// test DELETE
XCTAssertEqual(Int(CEDeleteCharacter), NSDeleteCharacter)
let deleteCharacter = CEUnicodeCharacter(character: UTF32Char(NSDeleteCharacter))
let deletePictureCharacter = CEUnicodeCharacter(character: UTF32Char(""))
XCTAssertEqual(Int(ControlCharacter.deleteCharacter), NSDeleteCharacter)
let deleteCharacter = UnicodeCharacter(character: UnicodeScalar(NSDeleteCharacter))
let deletePictureCharacter = UnicodeCharacter(character: UnicodeScalar(""))
XCTAssertEqual(deleteCharacter.name, "DELETE")
XCTAssertEqual(deletePictureCharacter.name, "SYMBOL FOR DELETE")
XCTAssertEqual(deleteCharacter.pictureCharacter, unichar(deletePictureCharacter.character))
XCTAssertEqual(deleteCharacter.pictureCharacter, deletePictureCharacter.character)
// test one after the last C0 control character
let exclamationCharacter = CEUnicodeCharacter(character: UTF32Char(0x0021))
let exclamationCharacter = UnicodeCharacter(character: UnicodeScalar(0x0021))
XCTAssertEqual(exclamationCharacter.name, "EXCLAMATION MARK")
XCTAssertEqual(exclamationCharacter.pictureCharacter, 0)
XCTAssertNil(exclamationCharacter.pictureCharacter)
}
// MARK: - CharacterInfo Tests
func testMultiCharString() {
XCTAssertNil(CharacterInfo(string: "foo"))
}
func testSingleCharWithVSInfo() {
guard let charInfo = CharacterInfo(string: "☺︎") else {
XCTFail()
return
@ -127,36 +138,39 @@ class CharacterInfoTests: XCTestCase {
XCTAssertEqual(charInfo.string, "☺︎")
XCTAssertFalse(charInfo.isComplex)
XCTAssertEqual(charInfo.unicodes.map{$0.unicode}, ["U+263A", "U+FE0E"])
XCTAssertEqual(charInfo.unicodes.map{$0.name}, ["WHITE SMILING FACE", "VARIATION SELECTOR-15"])
XCTAssertEqual(charInfo.unicodes.map{ $0.unicode }, ["U+263A", "U+FE0E"])
XCTAssertEqual(charInfo.unicodes.map{ $0.name }, ["WHITE SMILING FACE", "VARIATION SELECTOR-15"])
XCTAssertEqual(charInfo.localizedDescription, "WHITE SMILING FACE (Text Style)")
}
func testCombiningCharacterInfo() {
guard let charInfo = CharacterInfo(string: "1") else {
XCTFail()
return
}
XCTAssertTrue(charInfo.isComplex)
XCTAssertEqual(charInfo.unicodes.map{$0.unicode}, ["U+0031", "U+FE0F", "U+20E3"])
XCTAssertEqual(charInfo.unicodes.map{ $0.unicode }, ["U+0031", "U+FE0F", "U+20E3"])
XCTAssertEqual(charInfo.localizedDescription, "<a letter consisting of 3 characters>")
}
func testNationalIndicatorInfo() {
guard let charInfo = CharacterInfo(string: "🇯🇵") else {
XCTFail()
return
}
XCTAssertTrue(charInfo.isComplex)
XCTAssertEqual(charInfo.unicodes.map{$0.unicode}, ["U+1F1EF", "U+1F1F5"])
XCTAssertEqual(charInfo.unicodes.map{ $0.unicode }, ["U+1F1EF", "U+1F1F5"])
}
func testControlCharacterInfo() {
guard let charInfo = CharacterInfo(string: " ") else {
XCTFail()
return
@ -164,7 +178,7 @@ class CharacterInfoTests: XCTestCase {
XCTAssertEqual(charInfo.string, " ")
XCTAssertEqual(charInfo.pictureString, "")
XCTAssertEqual(charInfo.unicodes.map{$0.name}, ["SPACE"])
XCTAssertEqual(charInfo.unicodes.map{ $0.name }, ["SPACE"])
}
}

View File

@ -7,9 +7,5 @@
#import <NSHash/NSString+NSHash.h>
#import "CESyntaxDictionaryKeys.h"
// Character Info Tests
#import "CEControlCharacerNames.h"
#import "CEUnicodeCharacter.h"
// Theme Tests
#import "NSColor+WFColorCode.h"