mirror of
https://github.com/Toxblh/MTMR.git
synced 2026-01-10 00:58:37 +00:00
Add new "shellScriptTitledButton" button type
This commit is contained in:
parent
580f0275fc
commit
80b56779fb
@ -18,6 +18,8 @@
|
||||
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
|
||||
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
|
||||
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
|
||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
|
||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
|
||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
|
||||
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; };
|
||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
|
||||
@ -92,6 +94,9 @@
|
||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
|
||||
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
|
||||
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
|
||||
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
|
||||
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
|
||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
|
||||
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
|
||||
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = "<group>"; };
|
||||
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = "<group>"; };
|
||||
@ -222,6 +227,7 @@
|
||||
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
|
||||
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
|
||||
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
|
||||
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
|
||||
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
|
||||
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
|
||||
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
|
||||
@ -266,6 +272,8 @@
|
||||
B0B1743B207D6ED40004B740 /* CBridge */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */,
|
||||
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */,
|
||||
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
|
||||
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
|
||||
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
|
||||
@ -459,6 +467,7 @@
|
||||
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
|
||||
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
|
||||
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
|
||||
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
|
||||
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
|
||||
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
|
||||
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
|
||||
@ -472,6 +481,7 @@
|
||||
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
|
||||
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
|
||||
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
|
||||
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */,
|
||||
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
|
||||
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
|
||||
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
|
||||
|
||||
326
MTMR/CBridge/AMR_ANSIEscapeHelper.h
Normal file
326
MTMR/CBridge/AMR_ANSIEscapeHelper.h
Normal file
@ -0,0 +1,326 @@
|
||||
//
|
||||
// ANSIEscapeHelper.h
|
||||
// AnsiColorsTest
|
||||
//
|
||||
// Created by Ali Rantakari on 18.3.09.
|
||||
//
|
||||
// Version 0.9.6
|
||||
//
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008-2009,2013 Ali Rantakari
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#warning "This code requires ARC to be enabled."
|
||||
#endif
|
||||
|
||||
|
||||
// dictionary keys for the SGR code dictionaries that the array
|
||||
// escapeCodesForString:cleanString: returns contains
|
||||
#define kAMRCodeDictKey_code @"code"
|
||||
#define kAMRCodeDictKey_location @"location"
|
||||
|
||||
// dictionary keys for the string formatting attribute
|
||||
// dictionaries that the array attributesForString:cleanString:
|
||||
// returns contains
|
||||
#define kAMRAttrDictKey_range @"range"
|
||||
#define kAMRAttrDictKey_attrName @"attributeName"
|
||||
#define kAMRAttrDictKey_attrValue @"attributeValue"
|
||||
|
||||
|
||||
/*!
|
||||
@enum AMR_SGRCode
|
||||
|
||||
@abstract SGR (Select Graphic Rendition) ANSI control codes.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
AMR_SGRCodeNoneOrInvalid = -1,
|
||||
|
||||
AMR_SGRCodeAllReset = 0,
|
||||
|
||||
AMR_SGRCodeIntensityBold = 1,
|
||||
AMR_SGRCodeIntensityFaint = 2,
|
||||
AMR_SGRCodeIntensityNormal = 22,
|
||||
|
||||
AMR_SGRCodeItalicOn = 3,
|
||||
|
||||
AMR_SGRCodeUnderlineSingle = 4,
|
||||
AMR_SGRCodeUnderlineDouble = 21,
|
||||
AMR_SGRCodeUnderlineNone = 24,
|
||||
|
||||
AMR_SGRCodeFgBlack = 30,
|
||||
AMR_SGRCodeFgRed = 31,
|
||||
AMR_SGRCodeFgGreen = 32,
|
||||
AMR_SGRCodeFgYellow = 33,
|
||||
AMR_SGRCodeFgBlue = 34,
|
||||
AMR_SGRCodeFgMagenta = 35,
|
||||
AMR_SGRCodeFgCyan = 36,
|
||||
AMR_SGRCodeFgWhite = 37,
|
||||
AMR_SGRCodeFgReset = 39,
|
||||
|
||||
AMR_SGRCodeBgBlack = 40,
|
||||
AMR_SGRCodeBgRed = 41,
|
||||
AMR_SGRCodeBgGreen = 42,
|
||||
AMR_SGRCodeBgYellow = 43,
|
||||
AMR_SGRCodeBgBlue = 44,
|
||||
AMR_SGRCodeBgMagenta = 45,
|
||||
AMR_SGRCodeBgCyan = 46,
|
||||
AMR_SGRCodeBgWhite = 47,
|
||||
AMR_SGRCodeBgReset = 49,
|
||||
|
||||
AMR_SGRCodeFgBrightBlack = 90,
|
||||
AMR_SGRCodeFgBrightRed = 91,
|
||||
AMR_SGRCodeFgBrightGreen = 92,
|
||||
AMR_SGRCodeFgBrightYellow = 93,
|
||||
AMR_SGRCodeFgBrightBlue = 94,
|
||||
AMR_SGRCodeFgBrightMagenta = 95,
|
||||
AMR_SGRCodeFgBrightCyan = 96,
|
||||
AMR_SGRCodeFgBrightWhite = 97,
|
||||
|
||||
AMR_SGRCodeBgBrightBlack = 100,
|
||||
AMR_SGRCodeBgBrightRed = 101,
|
||||
AMR_SGRCodeBgBrightGreen = 102,
|
||||
AMR_SGRCodeBgBrightYellow = 103,
|
||||
AMR_SGRCodeBgBrightBlue = 104,
|
||||
AMR_SGRCodeBgBrightMagenta = 105,
|
||||
AMR_SGRCodeBgBrightCyan = 106,
|
||||
AMR_SGRCodeBgBrightWhite = 107
|
||||
} AMR_SGRCode;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@class AMR_ANSIEscapeHelper
|
||||
|
||||
@abstract Contains helper methods for dealing with strings
|
||||
that contain ANSI escape sequences for formatting (colors,
|
||||
underlining, bold etc.)
|
||||
*/
|
||||
@interface AMR_ANSIEscapeHelper : NSObject
|
||||
|
||||
/*!
|
||||
@property defaultStringColor
|
||||
|
||||
@abstract The default color used when creating an attributed string (default is black).
|
||||
*/
|
||||
@property(copy) NSColor *defaultStringColor;
|
||||
|
||||
|
||||
/*!
|
||||
@property font
|
||||
|
||||
@abstract The font to use when creating string formatting attribute values.
|
||||
*/
|
||||
@property(copy) NSFont *font;
|
||||
|
||||
/*!
|
||||
@property ansiColors
|
||||
|
||||
@abstract The colors to use for displaying ANSI colors.
|
||||
|
||||
@discussion Keys in this dictionary should be NSNumber objects containing SGR code
|
||||
values from the AMR_SGRCode enum. The corresponding values for these keys
|
||||
should be NSColor objects. If this property is nil or if it doesn't
|
||||
contain a key for a specific SGR code, the default color will be used
|
||||
instead.
|
||||
*/
|
||||
@property(retain) NSMutableDictionary *ansiColors;
|
||||
|
||||
|
||||
/*!
|
||||
@method attributedStringWithANSIEscapedString:
|
||||
|
||||
@abstract Returns an attributed string that corresponds both in contents
|
||||
and formatting to a given string that contains ANSI escape
|
||||
sequences.
|
||||
|
||||
@param aString A String containing ANSI escape sequences
|
||||
|
||||
@result An attributed string that mimics as closely as possible
|
||||
the formatting of the given ANSI-escaped string.
|
||||
*/
|
||||
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
|
||||
|
||||
|
||||
/*!
|
||||
@method ansiEscapedStringWithAttributedString:
|
||||
|
||||
@abstract Returns a string containing ANSI escape sequences that corresponds
|
||||
both in contents and formatting to a given attributed string.
|
||||
|
||||
@param aAttributedString An attributed string
|
||||
|
||||
@result A string that mimics as closely as possible
|
||||
the formatting of the given attributed string with
|
||||
ANSI escape sequences.
|
||||
*/
|
||||
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
|
||||
|
||||
|
||||
/*!
|
||||
@method escapeCodesForString:cleanString:
|
||||
|
||||
@abstract Returns an array of SGR codes and their locations from a
|
||||
string containing ANSI escape sequences as well as a "clean"
|
||||
version of the string (i.e. one without the ANSI escape
|
||||
sequences.)
|
||||
|
||||
@param aString A String containing ANSI escape sequences
|
||||
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
|
||||
without the ANSI escape sequences)
|
||||
|
||||
@result An array of NSDictionary objects, each of which has
|
||||
an NSNumber value for the key "code" (specifying an SGR code) and
|
||||
another NSNumber value for the key "location" (specifying the
|
||||
location of the code within aCleanString.)
|
||||
*/
|
||||
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
|
||||
|
||||
|
||||
/*!
|
||||
@method ansiEscapedStringWithCodesAndLocations:cleanString:
|
||||
|
||||
@abstract Returns a string containing ANSI escape codes for formatting based
|
||||
on a string and an array of SGR codes and their locations within
|
||||
the given string.
|
||||
|
||||
@param aCodesArray An array of NSDictionary objects, each of which should have
|
||||
an NSNumber value for the key "code" (specifying an SGR
|
||||
code) and another NSNumber value for the key "location"
|
||||
(specifying the location of this SGR code in aCleanString.)
|
||||
@param aCleanString The string to which to insert the ANSI escape codes
|
||||
described in aCodesArray.
|
||||
|
||||
@result A string containing ANSI escape sequences.
|
||||
*/
|
||||
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
|
||||
|
||||
|
||||
/*!
|
||||
@method attributesForString:cleanString:
|
||||
|
||||
@abstract Convert ANSI escape sequences in a string to string formatting attributes.
|
||||
|
||||
@discussion Given a string with some ANSI escape sequences in it, this method returns
|
||||
attributes for formatting the specified string according to those ANSI
|
||||
escape sequences as well as a "clean" (i.e. free of the escape sequences)
|
||||
version of this string.
|
||||
|
||||
@param aString A String containing ANSI escape sequences
|
||||
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
|
||||
without the ANSI escape sequences.) Pass in NULL if you're not
|
||||
interested in this.
|
||||
|
||||
@result An array containing NSDictionary objects, each of which has keys "range"
|
||||
(an NSValue containing an NSRange, specifying the range for the
|
||||
attribute within the "clean" version of aString), "attributeName" (an
|
||||
NSString) and "attributeValue" (an NSObject). You may use these as
|
||||
arguments for NSMutableAttributedString's methods for setting the
|
||||
visual formatting.
|
||||
*/
|
||||
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
|
||||
|
||||
|
||||
/*!
|
||||
@method AMR_SGRCode:endsFormattingIntroducedByCode:
|
||||
|
||||
@abstract Whether the occurrence of a given SGR code would end the formatting run
|
||||
introduced by another SGR code.
|
||||
|
||||
@discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
|
||||
specifying a foreground color would end the formatting run
|
||||
introduced by a foreground color -specifying SGR code.
|
||||
|
||||
@param endCode The SGR code to test as a candidate for ending the formatting run
|
||||
introduced by startCode
|
||||
@param startCode The SGR code that has introduced a formatting run
|
||||
|
||||
@result YES if the occurrence of endCode would end the formatting run
|
||||
introduced by startCode, NO otherwise.
|
||||
*/
|
||||
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
|
||||
|
||||
|
||||
/*!
|
||||
@method colorForSGRCode:
|
||||
|
||||
@abstract Returns the color to use for displaying a specific ANSI color.
|
||||
|
||||
@discussion This method first considers the values set in the ansiColors
|
||||
property and only then the standard basic colors (NSColor's
|
||||
redColor, blueColor etc.)
|
||||
|
||||
@param code An SGR code that specifies an ANSI color.
|
||||
|
||||
@result The color to use for displaying the ANSI color specified by code.
|
||||
*/
|
||||
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
|
||||
|
||||
|
||||
/*!
|
||||
@method AMR_SGRCodeForColor:isForegroundColor:
|
||||
|
||||
@abstract Returns a color SGR code that corresponds to a given color.
|
||||
|
||||
@discussion This method matches colors to their equivalent SGR codes
|
||||
by going through the colors specified in the ansiColors
|
||||
dictionary, and if ansiColors is null or if a match is
|
||||
not found there, by comparing the given color to the
|
||||
standard basic colors (NSColor's redColor, blueColor
|
||||
etc.) The comparison is done simply by checking for
|
||||
equality.
|
||||
|
||||
@param aColor The color to get a corresponding SGR code for
|
||||
@param aForeground Whether you want a foreground or background color code
|
||||
|
||||
@result SGR code that corresponds with aColor.
|
||||
*/
|
||||
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
|
||||
|
||||
|
||||
/*!
|
||||
@method closestSGRCodeForColor:isForegroundColor:
|
||||
|
||||
@abstract Returns a color SGR code that represents the closest ANSI
|
||||
color to a given color.
|
||||
|
||||
@discussion This method attempts to find the closest ANSI color to
|
||||
aColor and return its SGR code.
|
||||
|
||||
@param color The color to get a closest color SGR code match for
|
||||
@param foreground Whether you want a foreground or background color code
|
||||
|
||||
@result SGR code for the ANSI color that is closest to aColor.
|
||||
*/
|
||||
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
|
||||
|
||||
|
||||
|
||||
@end
|
||||
996
MTMR/CBridge/AMR_ANSIEscapeHelper.m
Normal file
996
MTMR/CBridge/AMR_ANSIEscapeHelper.m
Normal file
@ -0,0 +1,996 @@
|
||||
//
|
||||
// ANSIEscapeHelper.m
|
||||
//
|
||||
// Created by Ali Rantakari on 18.3.09.
|
||||
|
||||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2008-2009,2013 Ali Rantakari
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
todo:
|
||||
|
||||
- don't add useless "reset" escape codes to the string in
|
||||
-ansiEscapedStringWithAttributedString:
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#import "AMR_ANSIEscapeHelper.h"
|
||||
|
||||
|
||||
// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
|
||||
// (add your own CSI:Miami joke here)
|
||||
#define kANSIEscapeCSI @"\033["
|
||||
|
||||
// the end byte of an SGR (Select Graphic Rendition)
|
||||
// ANSI Escape Sequence
|
||||
#define kANSIEscapeSGREnd @"m"
|
||||
|
||||
|
||||
// color definition helper macros
|
||||
#define kBrightColorBrightness 1.0
|
||||
#define kBrightColorSaturation 0.4
|
||||
#define kBrightColorAlpha 1.0
|
||||
#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
|
||||
|
||||
// default colors
|
||||
#define kDefaultANSIColorFgBlack NSColor.blackColor
|
||||
#define kDefaultANSIColorFgRed NSColor.redColor
|
||||
#define kDefaultANSIColorFgGreen NSColor.greenColor
|
||||
#define kDefaultANSIColorFgYellow NSColor.yellowColor
|
||||
#define kDefaultANSIColorFgBlue NSColor.blueColor
|
||||
#define kDefaultANSIColorFgMagenta NSColor.magentaColor
|
||||
#define kDefaultANSIColorFgCyan NSColor.cyanColor
|
||||
#define kDefaultANSIColorFgWhite NSColor.whiteColor
|
||||
|
||||
#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
|
||||
#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0)
|
||||
#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0)
|
||||
#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0)
|
||||
#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0)
|
||||
#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0)
|
||||
#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5)
|
||||
#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor
|
||||
|
||||
#define kDefaultANSIColorBgBlack NSColor.blackColor
|
||||
#define kDefaultANSIColorBgRed NSColor.redColor
|
||||
#define kDefaultANSIColorBgGreen NSColor.greenColor
|
||||
#define kDefaultANSIColorBgYellow NSColor.yellowColor
|
||||
#define kDefaultANSIColorBgBlue NSColor.blueColor
|
||||
#define kDefaultANSIColorBgMagenta NSColor.magentaColor
|
||||
#define kDefaultANSIColorBgCyan NSColor.cyanColor
|
||||
#define kDefaultANSIColorBgWhite NSColor.whiteColor
|
||||
|
||||
#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack
|
||||
#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed
|
||||
#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen
|
||||
#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow
|
||||
#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue
|
||||
#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta
|
||||
#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan
|
||||
#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite
|
||||
|
||||
#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
|
||||
#define kDefaultForegroundColor NSColor.blackColor
|
||||
|
||||
// minimum weight for an NSFont for it to be considered bold
|
||||
#define kBoldFontMinWeight 9
|
||||
|
||||
|
||||
@implementation AMR_ANSIEscapeHelper
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
self.ansiColors = [NSMutableDictionary dictionary];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
|
||||
{
|
||||
if (aString == nil)
|
||||
return nil;
|
||||
|
||||
NSString *cleanString;
|
||||
NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
|
||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
|
||||
initWithString:cleanString
|
||||
attributes:@{
|
||||
NSFontAttributeName: self.font ?: kDefaultFontSize,
|
||||
NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
|
||||
}];
|
||||
|
||||
for (NSDictionary *thisAttributeDict in attributesAndRanges)
|
||||
{
|
||||
[attributedString
|
||||
addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
|
||||
value:thisAttributeDict[kAMRAttrDictKey_attrValue]
|
||||
range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
|
||||
];
|
||||
}
|
||||
|
||||
return attributedString;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
|
||||
{
|
||||
NSMutableArray *codesAndLocations = [NSMutableArray array];
|
||||
|
||||
NSArray *attrNames = @[
|
||||
NSFontAttributeName, NSForegroundColorAttributeName,
|
||||
NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
|
||||
];
|
||||
|
||||
for (NSString *thisAttrName in attrNames)
|
||||
{
|
||||
NSRange limitRange = NSMakeRange(0, aAttributedString.length);
|
||||
id attributeValue;
|
||||
NSRange effectiveRange;
|
||||
|
||||
while (limitRange.length > 0)
|
||||
{
|
||||
attributeValue = [aAttributedString
|
||||
attribute:thisAttrName
|
||||
atIndex:limitRange.location
|
||||
longestEffectiveRange:&effectiveRange
|
||||
inRange:limitRange
|
||||
];
|
||||
|
||||
AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
|
||||
|
||||
if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
|
||||
{
|
||||
if (attributeValue != nil)
|
||||
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
|
||||
else
|
||||
thisSGRCode = AMR_SGRCodeFgReset;
|
||||
}
|
||||
else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
|
||||
{
|
||||
if (attributeValue != nil)
|
||||
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
|
||||
else
|
||||
thisSGRCode = AMR_SGRCodeBgReset;
|
||||
}
|
||||
else if ([thisAttrName isEqualToString:NSFontAttributeName])
|
||||
{
|
||||
// we currently only use NSFontAttributeName for bolding so
|
||||
// here we assume that the formatting "type" in ANSI SGR
|
||||
// terms is indeed intensity
|
||||
if (attributeValue != nil)
|
||||
thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
|
||||
? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
|
||||
else
|
||||
thisSGRCode = AMR_SGRCodeIntensityNormal;
|
||||
}
|
||||
else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
|
||||
{
|
||||
if (attributeValue != nil)
|
||||
{
|
||||
if ([attributeValue intValue] == NSUnderlineStyleSingle)
|
||||
thisSGRCode = AMR_SGRCodeUnderlineSingle;
|
||||
else if ([attributeValue intValue] == NSUnderlineStyleDouble)
|
||||
thisSGRCode = AMR_SGRCodeUnderlineDouble;
|
||||
else
|
||||
thisSGRCode = AMR_SGRCodeUnderlineNone;
|
||||
}
|
||||
else
|
||||
thisSGRCode = AMR_SGRCodeUnderlineNone;
|
||||
}
|
||||
|
||||
if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
|
||||
{
|
||||
[codesAndLocations addObject: @{
|
||||
kAMRCodeDictKey_code: @(thisSGRCode),
|
||||
kAMRCodeDictKey_location: @(effectiveRange.location),
|
||||
}];
|
||||
}
|
||||
|
||||
limitRange = NSMakeRange(NSMaxRange(effectiveRange),
|
||||
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
|
||||
}
|
||||
}
|
||||
|
||||
return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
|
||||
{
|
||||
if (aString == nil)
|
||||
return nil;
|
||||
if (aString.length <= kANSIEscapeCSI.length)
|
||||
{
|
||||
if (aCleanString)
|
||||
*aCleanString = aString.copy;
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSString *cleanString = @"";
|
||||
|
||||
// find all escape sequence codes from aString and put them in this array
|
||||
// along with their start locations within the "clean" version of aString
|
||||
NSMutableArray *formatCodes = [NSMutableArray array];
|
||||
|
||||
NSUInteger aStringLength = aString.length;
|
||||
NSUInteger coveredLength = 0;
|
||||
NSRange searchRange = NSMakeRange(0,aStringLength);
|
||||
NSRange thisEscapeSequenceRange;
|
||||
do
|
||||
{
|
||||
thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
|
||||
if (thisEscapeSequenceRange.location != NSNotFound)
|
||||
{
|
||||
// adjust range's length so that it encompasses the whole ANSI escape sequence
|
||||
// and not just the Control Sequence Initiator (the "prefix") by finding the
|
||||
// final byte of the control sequence (one that has an ASCII decimal value
|
||||
// between 64 and 126.) at the same time, read all formatting codes from inside
|
||||
// this escape sequence (there may be several, separated by semicolons.)
|
||||
NSMutableArray *codes = [NSMutableArray array];
|
||||
unsigned int code = 0;
|
||||
unsigned int lengthAddition = 1;
|
||||
NSUInteger thisIndex;
|
||||
for (;;)
|
||||
{
|
||||
thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
|
||||
if (thisIndex >= aStringLength)
|
||||
break;
|
||||
|
||||
unichar c = [aString characterAtIndex:thisIndex];
|
||||
|
||||
if (('0' <= c) && (c <= '9'))
|
||||
{
|
||||
int digit = c - '0';
|
||||
code = (code == 0) ? digit : code*10+digit;
|
||||
}
|
||||
|
||||
// ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
|
||||
// ("m"). this means that the code value we've just read specifies formatting
|
||||
// for the output; exactly what we're interested in.
|
||||
if (c == 'm')
|
||||
{
|
||||
[codes addObject:@(code)];
|
||||
break;
|
||||
}
|
||||
else if ((64 <= c) && (c <= 126)) // any other valid final byte
|
||||
{
|
||||
[codes removeAllObjects];
|
||||
break;
|
||||
}
|
||||
else if (c == ';') // separates codes within the same sequence
|
||||
{
|
||||
[codes addObject:@(code)];
|
||||
code = 0;
|
||||
}
|
||||
|
||||
lengthAddition++;
|
||||
}
|
||||
thisEscapeSequenceRange.length += lengthAddition;
|
||||
|
||||
NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
|
||||
|
||||
for (NSNumber *codeToAdd in codes)
|
||||
{
|
||||
[formatCodes addObject: @{
|
||||
kAMRCodeDictKey_code: codeToAdd,
|
||||
kAMRCodeDictKey_location: @(locationInCleanString)
|
||||
}];
|
||||
}
|
||||
|
||||
NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
|
||||
if (thisCoveredLength > 0)
|
||||
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
|
||||
|
||||
coveredLength += thisCoveredLength;
|
||||
searchRange.location = NSMaxRange(thisEscapeSequenceRange);
|
||||
searchRange.length = aStringLength-searchRange.location;
|
||||
}
|
||||
}
|
||||
while(thisEscapeSequenceRange.location != NSNotFound);
|
||||
|
||||
if (searchRange.length > 0)
|
||||
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
|
||||
|
||||
if (aCleanString)
|
||||
*aCleanString = cleanString;
|
||||
return formatCodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
|
||||
{
|
||||
NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
|
||||
|
||||
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
|
||||
NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
|
||||
|
||||
NSUInteger aCleanStringIndex = 0;
|
||||
NSUInteger aCleanStringLength = aCleanString.length;
|
||||
for (NSDictionary *thisCodeDict in codesArray)
|
||||
{
|
||||
if (!( thisCodeDict[kAMRCodeDictKey_code] &&
|
||||
thisCodeDict[kAMRCodeDictKey_location]
|
||||
))
|
||||
continue;
|
||||
|
||||
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
|
||||
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
|
||||
|
||||
if (formattingRunStartLocation > aCleanStringLength)
|
||||
continue;
|
||||
|
||||
if (aCleanStringIndex < formattingRunStartLocation)
|
||||
[retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
|
||||
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
|
||||
|
||||
aCleanStringIndex = formattingRunStartLocation;
|
||||
}
|
||||
|
||||
if (aCleanStringIndex < aCleanStringLength)
|
||||
[retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
|
||||
|
||||
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
|
||||
|
||||
return retStr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
|
||||
{
|
||||
if (aString == nil)
|
||||
return nil;
|
||||
if (aString.length <= kANSIEscapeCSI.length)
|
||||
{
|
||||
if (aCleanString)
|
||||
*aCleanString = aString.copy;
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray *attrsAndRanges = [NSMutableArray array];
|
||||
|
||||
NSString *cleanString;
|
||||
NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
|
||||
|
||||
// go through all the found escape sequence codes and for each one, create
|
||||
// the string formatting attribute name and value, find the next escape
|
||||
// sequence that specifies the end of the formatting run started by
|
||||
// the currently handled code, and generate a range from the difference
|
||||
// in those codes' locations within the clean aString.
|
||||
for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
|
||||
{
|
||||
NSDictionary *thisCodeDict = formatCodes[iCode];
|
||||
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
|
||||
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
|
||||
|
||||
// the attributed string attribute name for the formatting run introduced
|
||||
// by this code
|
||||
NSString *thisAttributeName = nil;
|
||||
|
||||
// the attributed string attribute value for this formatting run introduced
|
||||
// by this code
|
||||
NSObject *thisAttributeValue = nil;
|
||||
|
||||
// set attribute name
|
||||
switch(thisCode)
|
||||
{
|
||||
case AMR_SGRCodeFgBlack:
|
||||
case AMR_SGRCodeFgRed:
|
||||
case AMR_SGRCodeFgGreen:
|
||||
case AMR_SGRCodeFgYellow:
|
||||
case AMR_SGRCodeFgBlue:
|
||||
case AMR_SGRCodeFgMagenta:
|
||||
case AMR_SGRCodeFgCyan:
|
||||
case AMR_SGRCodeFgWhite:
|
||||
case AMR_SGRCodeFgBrightBlack:
|
||||
case AMR_SGRCodeFgBrightRed:
|
||||
case AMR_SGRCodeFgBrightGreen:
|
||||
case AMR_SGRCodeFgBrightYellow:
|
||||
case AMR_SGRCodeFgBrightBlue:
|
||||
case AMR_SGRCodeFgBrightMagenta:
|
||||
case AMR_SGRCodeFgBrightCyan:
|
||||
case AMR_SGRCodeFgBrightWhite:
|
||||
thisAttributeName = NSForegroundColorAttributeName;
|
||||
break;
|
||||
case AMR_SGRCodeBgBlack:
|
||||
case AMR_SGRCodeBgRed:
|
||||
case AMR_SGRCodeBgGreen:
|
||||
case AMR_SGRCodeBgYellow:
|
||||
case AMR_SGRCodeBgBlue:
|
||||
case AMR_SGRCodeBgMagenta:
|
||||
case AMR_SGRCodeBgCyan:
|
||||
case AMR_SGRCodeBgWhite:
|
||||
case AMR_SGRCodeBgBrightBlack:
|
||||
case AMR_SGRCodeBgBrightRed:
|
||||
case AMR_SGRCodeBgBrightGreen:
|
||||
case AMR_SGRCodeBgBrightYellow:
|
||||
case AMR_SGRCodeBgBrightBlue:
|
||||
case AMR_SGRCodeBgBrightMagenta:
|
||||
case AMR_SGRCodeBgBrightCyan:
|
||||
case AMR_SGRCodeBgBrightWhite:
|
||||
thisAttributeName = NSBackgroundColorAttributeName;
|
||||
break;
|
||||
case AMR_SGRCodeIntensityBold:
|
||||
case AMR_SGRCodeIntensityNormal:
|
||||
case AMR_SGRCodeIntensityFaint:
|
||||
thisAttributeName = NSFontAttributeName;
|
||||
break;
|
||||
case AMR_SGRCodeUnderlineSingle:
|
||||
case AMR_SGRCodeUnderlineDouble:
|
||||
case AMR_SGRCodeUnderlineNone:
|
||||
thisAttributeName = NSUnderlineStyleAttributeName;
|
||||
break;
|
||||
case AMR_SGRCodeAllReset:
|
||||
case AMR_SGRCodeFgReset:
|
||||
case AMR_SGRCodeBgReset:
|
||||
case AMR_SGRCodeNoneOrInvalid:
|
||||
case AMR_SGRCodeItalicOn:
|
||||
continue;
|
||||
}
|
||||
|
||||
// set attribute value
|
||||
switch(thisCode)
|
||||
{
|
||||
case AMR_SGRCodeBgBlack:
|
||||
case AMR_SGRCodeFgBlack:
|
||||
case AMR_SGRCodeBgRed:
|
||||
case AMR_SGRCodeFgRed:
|
||||
case AMR_SGRCodeBgGreen:
|
||||
case AMR_SGRCodeFgGreen:
|
||||
case AMR_SGRCodeBgYellow:
|
||||
case AMR_SGRCodeFgYellow:
|
||||
case AMR_SGRCodeBgBlue:
|
||||
case AMR_SGRCodeFgBlue:
|
||||
case AMR_SGRCodeBgMagenta:
|
||||
case AMR_SGRCodeFgMagenta:
|
||||
case AMR_SGRCodeBgCyan:
|
||||
case AMR_SGRCodeFgCyan:
|
||||
case AMR_SGRCodeBgWhite:
|
||||
case AMR_SGRCodeFgWhite:
|
||||
case AMR_SGRCodeBgBrightBlack:
|
||||
case AMR_SGRCodeFgBrightBlack:
|
||||
case AMR_SGRCodeBgBrightRed:
|
||||
case AMR_SGRCodeFgBrightRed:
|
||||
case AMR_SGRCodeBgBrightGreen:
|
||||
case AMR_SGRCodeFgBrightGreen:
|
||||
case AMR_SGRCodeBgBrightYellow:
|
||||
case AMR_SGRCodeFgBrightYellow:
|
||||
case AMR_SGRCodeBgBrightBlue:
|
||||
case AMR_SGRCodeFgBrightBlue:
|
||||
case AMR_SGRCodeBgBrightMagenta:
|
||||
case AMR_SGRCodeFgBrightMagenta:
|
||||
case AMR_SGRCodeBgBrightCyan:
|
||||
case AMR_SGRCodeFgBrightCyan:
|
||||
case AMR_SGRCodeBgBrightWhite:
|
||||
case AMR_SGRCodeFgBrightWhite:
|
||||
thisAttributeValue = [self colorForSGRCode:thisCode];
|
||||
break;
|
||||
case AMR_SGRCodeIntensityBold:
|
||||
{
|
||||
NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
|
||||
thisAttributeValue = boldFont;
|
||||
}
|
||||
break;
|
||||
case AMR_SGRCodeIntensityNormal:
|
||||
case AMR_SGRCodeIntensityFaint:
|
||||
{
|
||||
NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
|
||||
thisAttributeValue = unboldFont;
|
||||
}
|
||||
break;
|
||||
case AMR_SGRCodeUnderlineSingle:
|
||||
thisAttributeValue = @(NSUnderlineStyleSingle);
|
||||
break;
|
||||
case AMR_SGRCodeUnderlineDouble:
|
||||
thisAttributeValue = @(NSUnderlineStyleDouble);
|
||||
break;
|
||||
case AMR_SGRCodeUnderlineNone:
|
||||
thisAttributeValue = @(NSUnderlineStyleNone);
|
||||
break;
|
||||
case AMR_SGRCodeAllReset:
|
||||
case AMR_SGRCodeFgReset:
|
||||
case AMR_SGRCodeBgReset:
|
||||
case AMR_SGRCodeNoneOrInvalid:
|
||||
case AMR_SGRCodeItalicOn:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// find the next sequence that specifies the end of this formatting run
|
||||
NSInteger formattingRunEndLocation = -1;
|
||||
if (iCode < (formatCodes.count - 1))
|
||||
{
|
||||
NSDictionary *thisEndCodeCandidateDict;
|
||||
unichar thisEndCodeCandidate;
|
||||
for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
|
||||
{
|
||||
thisEndCodeCandidateDict = formatCodes[iEndCode];
|
||||
thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
|
||||
|
||||
if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
|
||||
{
|
||||
formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (formattingRunEndLocation == -1)
|
||||
formattingRunEndLocation = cleanString.length;
|
||||
|
||||
if (thisAttributeName && thisAttributeValue)
|
||||
{
|
||||
[attrsAndRanges addObject:@{
|
||||
kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
|
||||
kAMRAttrDictKey_attrName: thisAttributeName,
|
||||
kAMRAttrDictKey_attrValue: thisAttributeValue,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (aCleanString)
|
||||
*aCleanString = cleanString;
|
||||
return attrsAndRanges;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
|
||||
{
|
||||
switch(startCode)
|
||||
{
|
||||
case AMR_SGRCodeFgBlack:
|
||||
case AMR_SGRCodeFgRed:
|
||||
case AMR_SGRCodeFgGreen:
|
||||
case AMR_SGRCodeFgYellow:
|
||||
case AMR_SGRCodeFgBlue:
|
||||
case AMR_SGRCodeFgMagenta:
|
||||
case AMR_SGRCodeFgCyan:
|
||||
case AMR_SGRCodeFgWhite:
|
||||
case AMR_SGRCodeFgBrightBlack:
|
||||
case AMR_SGRCodeFgBrightRed:
|
||||
case AMR_SGRCodeFgBrightGreen:
|
||||
case AMR_SGRCodeFgBrightYellow:
|
||||
case AMR_SGRCodeFgBrightBlue:
|
||||
case AMR_SGRCodeFgBrightMagenta:
|
||||
case AMR_SGRCodeFgBrightCyan:
|
||||
case AMR_SGRCodeFgBrightWhite:
|
||||
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
|
||||
endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
|
||||
endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
|
||||
endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
|
||||
endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
|
||||
endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
|
||||
endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
|
||||
endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
|
||||
endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
|
||||
case AMR_SGRCodeBgBlack:
|
||||
case AMR_SGRCodeBgRed:
|
||||
case AMR_SGRCodeBgGreen:
|
||||
case AMR_SGRCodeBgYellow:
|
||||
case AMR_SGRCodeBgBlue:
|
||||
case AMR_SGRCodeBgMagenta:
|
||||
case AMR_SGRCodeBgCyan:
|
||||
case AMR_SGRCodeBgWhite:
|
||||
case AMR_SGRCodeBgBrightBlack:
|
||||
case AMR_SGRCodeBgBrightRed:
|
||||
case AMR_SGRCodeBgBrightGreen:
|
||||
case AMR_SGRCodeBgBrightYellow:
|
||||
case AMR_SGRCodeBgBrightBlue:
|
||||
case AMR_SGRCodeBgBrightMagenta:
|
||||
case AMR_SGRCodeBgBrightCyan:
|
||||
case AMR_SGRCodeBgBrightWhite:
|
||||
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
|
||||
endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
|
||||
endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
|
||||
endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
|
||||
endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
|
||||
endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
|
||||
endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
|
||||
endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
|
||||
endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
|
||||
case AMR_SGRCodeIntensityBold:
|
||||
case AMR_SGRCodeIntensityNormal:
|
||||
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
|
||||
endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
|
||||
case AMR_SGRCodeUnderlineSingle:
|
||||
case AMR_SGRCodeUnderlineDouble:
|
||||
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
|
||||
endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
|
||||
case AMR_SGRCodeNoneOrInvalid:
|
||||
case AMR_SGRCodeItalicOn:
|
||||
case AMR_SGRCodeUnderlineNone:
|
||||
case AMR_SGRCodeIntensityFaint:
|
||||
case AMR_SGRCodeAllReset:
|
||||
case AMR_SGRCodeBgReset:
|
||||
case AMR_SGRCodeFgReset:
|
||||
return NO;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
|
||||
{
|
||||
if (self.ansiColors)
|
||||
{
|
||||
NSColor *preferredColor = self.ansiColors[@(code)];
|
||||
if (preferredColor)
|
||||
return preferredColor;
|
||||
}
|
||||
|
||||
switch(code)
|
||||
{
|
||||
case AMR_SGRCodeFgBlack:
|
||||
return kDefaultANSIColorFgBlack;
|
||||
case AMR_SGRCodeFgRed:
|
||||
return kDefaultANSIColorFgRed;
|
||||
case AMR_SGRCodeFgGreen:
|
||||
return kDefaultANSIColorFgGreen;
|
||||
case AMR_SGRCodeFgYellow:
|
||||
return kDefaultANSIColorFgYellow;
|
||||
case AMR_SGRCodeFgBlue:
|
||||
return kDefaultANSIColorFgBlue;
|
||||
case AMR_SGRCodeFgMagenta:
|
||||
return kDefaultANSIColorFgMagenta;
|
||||
case AMR_SGRCodeFgCyan:
|
||||
return kDefaultANSIColorFgCyan;
|
||||
case AMR_SGRCodeFgWhite:
|
||||
return kDefaultANSIColorFgWhite;
|
||||
case AMR_SGRCodeFgBrightBlack:
|
||||
return kDefaultANSIColorFgBrightBlack;
|
||||
case AMR_SGRCodeFgBrightRed:
|
||||
return kDefaultANSIColorFgBrightRed;
|
||||
case AMR_SGRCodeFgBrightGreen:
|
||||
return kDefaultANSIColorFgBrightGreen;
|
||||
case AMR_SGRCodeFgBrightYellow:
|
||||
return kDefaultANSIColorFgBrightYellow;
|
||||
case AMR_SGRCodeFgBrightBlue:
|
||||
return kDefaultANSIColorFgBrightBlue;
|
||||
case AMR_SGRCodeFgBrightMagenta:
|
||||
return kDefaultANSIColorFgBrightMagenta;
|
||||
case AMR_SGRCodeFgBrightCyan:
|
||||
return kDefaultANSIColorFgBrightCyan;
|
||||
case AMR_SGRCodeFgBrightWhite:
|
||||
return kDefaultANSIColorFgBrightWhite;
|
||||
case AMR_SGRCodeBgBlack:
|
||||
return kDefaultANSIColorBgBlack;
|
||||
case AMR_SGRCodeBgRed:
|
||||
return kDefaultANSIColorBgRed;
|
||||
case AMR_SGRCodeBgGreen:
|
||||
return kDefaultANSIColorBgGreen;
|
||||
case AMR_SGRCodeBgYellow:
|
||||
return kDefaultANSIColorBgYellow;
|
||||
case AMR_SGRCodeBgBlue:
|
||||
return kDefaultANSIColorBgBlue;
|
||||
case AMR_SGRCodeBgMagenta:
|
||||
return kDefaultANSIColorBgMagenta;
|
||||
case AMR_SGRCodeBgCyan:
|
||||
return kDefaultANSIColorBgCyan;
|
||||
case AMR_SGRCodeBgWhite:
|
||||
return kDefaultANSIColorBgWhite;
|
||||
case AMR_SGRCodeBgBrightBlack:
|
||||
return kDefaultANSIColorBgBrightBlack;
|
||||
case AMR_SGRCodeBgBrightRed:
|
||||
return kDefaultANSIColorBgBrightRed;
|
||||
case AMR_SGRCodeBgBrightGreen:
|
||||
return kDefaultANSIColorBgBrightGreen;
|
||||
case AMR_SGRCodeBgBrightYellow:
|
||||
return kDefaultANSIColorBgBrightYellow;
|
||||
case AMR_SGRCodeBgBrightBlue:
|
||||
return kDefaultANSIColorBgBrightBlue;
|
||||
case AMR_SGRCodeBgBrightMagenta:
|
||||
return kDefaultANSIColorBgBrightMagenta;
|
||||
case AMR_SGRCodeBgBrightCyan:
|
||||
return kDefaultANSIColorBgBrightCyan;
|
||||
case AMR_SGRCodeBgBrightWhite:
|
||||
return kDefaultANSIColorBgBrightWhite;
|
||||
case AMR_SGRCodeNoneOrInvalid:
|
||||
case AMR_SGRCodeItalicOn:
|
||||
case AMR_SGRCodeUnderlineNone:
|
||||
case AMR_SGRCodeIntensityFaint:
|
||||
case AMR_SGRCodeAllReset:
|
||||
case AMR_SGRCodeBgReset:
|
||||
case AMR_SGRCodeFgReset:
|
||||
case AMR_SGRCodeIntensityBold:
|
||||
case AMR_SGRCodeIntensityNormal:
|
||||
case AMR_SGRCodeUnderlineSingle:
|
||||
case AMR_SGRCodeUnderlineDouble:
|
||||
break;
|
||||
}
|
||||
|
||||
return kDefaultANSIColorFgBlack;
|
||||
}
|
||||
|
||||
|
||||
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
|
||||
{
|
||||
if (self.ansiColors)
|
||||
{
|
||||
NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
|
||||
|
||||
if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
|
||||
{
|
||||
for (NSNumber *thisCode in codesForGivenColor)
|
||||
{
|
||||
BOOL thisIsForegroundColor = (thisCode.intValue < 40);
|
||||
if (aForeground == thisIsForegroundColor)
|
||||
return thisCode.intValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aForeground)
|
||||
{
|
||||
if ([aColor isEqual:kDefaultANSIColorFgBlack])
|
||||
return AMR_SGRCodeFgBlack;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgRed])
|
||||
return AMR_SGRCodeFgRed;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgGreen])
|
||||
return AMR_SGRCodeFgGreen;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgYellow])
|
||||
return AMR_SGRCodeFgYellow;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBlue])
|
||||
return AMR_SGRCodeFgBlue;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
|
||||
return AMR_SGRCodeFgMagenta;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgCyan])
|
||||
return AMR_SGRCodeFgCyan;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgWhite])
|
||||
return AMR_SGRCodeFgWhite;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
|
||||
return AMR_SGRCodeFgBrightBlack;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
|
||||
return AMR_SGRCodeFgBrightRed;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
|
||||
return AMR_SGRCodeFgBrightGreen;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
|
||||
return AMR_SGRCodeFgBrightYellow;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
|
||||
return AMR_SGRCodeFgBrightBlue;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
|
||||
return AMR_SGRCodeFgBrightMagenta;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
|
||||
return AMR_SGRCodeFgBrightCyan;
|
||||
else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
|
||||
return AMR_SGRCodeFgBrightWhite;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([aColor isEqual:kDefaultANSIColorBgBlack])
|
||||
return AMR_SGRCodeBgBlack;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgRed])
|
||||
return AMR_SGRCodeBgRed;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgGreen])
|
||||
return AMR_SGRCodeBgGreen;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgYellow])
|
||||
return AMR_SGRCodeBgYellow;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBlue])
|
||||
return AMR_SGRCodeBgBlue;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
|
||||
return AMR_SGRCodeBgMagenta;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgCyan])
|
||||
return AMR_SGRCodeBgCyan;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgWhite])
|
||||
return AMR_SGRCodeBgWhite;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
|
||||
return AMR_SGRCodeBgBrightBlack;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
|
||||
return AMR_SGRCodeBgBrightRed;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
|
||||
return AMR_SGRCodeBgBrightGreen;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
|
||||
return AMR_SGRCodeBgBrightYellow;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
|
||||
return AMR_SGRCodeBgBrightBlue;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
|
||||
return AMR_SGRCodeBgBrightMagenta;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
|
||||
return AMR_SGRCodeBgBrightCyan;
|
||||
else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
|
||||
return AMR_SGRCodeBgBrightWhite;
|
||||
}
|
||||
|
||||
return AMR_SGRCodeNoneOrInvalid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// helper struct typedef and a few functions for
|
||||
// -closestSGRCodeForColor:isForegroundColor:
|
||||
|
||||
typedef struct {
|
||||
CGFloat hue;
|
||||
CGFloat saturation;
|
||||
CGFloat brightness;
|
||||
} AMR_HSB;
|
||||
|
||||
AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
|
||||
{
|
||||
AMR_HSB outHSB;
|
||||
outHSB.hue = hue;
|
||||
outHSB.saturation = saturation;
|
||||
outHSB.brightness = brightness;
|
||||
return outHSB;
|
||||
}
|
||||
|
||||
AMR_HSB getHSBFromColor(NSColor *color)
|
||||
{
|
||||
CGFloat hue = 0.0;
|
||||
CGFloat saturation = 0.0;
|
||||
CGFloat brightness = 0.0;
|
||||
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
|
||||
getHue:&hue
|
||||
saturation:&saturation
|
||||
brightness:&brightness
|
||||
alpha:NULL
|
||||
];
|
||||
return makeHSB(hue, saturation, brightness);
|
||||
}
|
||||
|
||||
BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
|
||||
{
|
||||
return (fabs(first-second)) < maxAbsError;
|
||||
}
|
||||
|
||||
#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
|
||||
|
||||
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
|
||||
{
|
||||
if (color == nil)
|
||||
return AMR_SGRCodeNoneOrInvalid;
|
||||
|
||||
AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
|
||||
if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
|
||||
return closestColorSGRCode;
|
||||
|
||||
AMR_HSB givenColorHSB = getHSBFromColor(color);
|
||||
|
||||
CGFloat closestColorHueDiff = FLT_MAX;
|
||||
CGFloat closestColorSaturationDiff = FLT_MAX;
|
||||
CGFloat closestColorBrightnessDiff = FLT_MAX;
|
||||
|
||||
// (background SGR codes are +10 from foreground ones:)
|
||||
NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
|
||||
NSArray *ansiFgColorCodes = @[
|
||||
@(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
|
||||
@(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
|
||||
];
|
||||
for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
|
||||
{
|
||||
AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
|
||||
NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
|
||||
|
||||
AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
|
||||
|
||||
CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
|
||||
CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
|
||||
CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
|
||||
|
||||
// comparison depends on hue, saturation and brightness
|
||||
// (strictly in that order):
|
||||
|
||||
if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
|
||||
{
|
||||
if (hueDiff > closestColorHueDiff)
|
||||
continue;
|
||||
closestColorSGRCode = thisSGRCode;
|
||||
closestColorHueDiff = hueDiff;
|
||||
closestColorSaturationDiff = saturationDiff;
|
||||
closestColorBrightnessDiff = brightnessDiff;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
|
||||
{
|
||||
if (saturationDiff > closestColorSaturationDiff)
|
||||
continue;
|
||||
closestColorSGRCode = thisSGRCode;
|
||||
closestColorHueDiff = hueDiff;
|
||||
closestColorSaturationDiff = saturationDiff;
|
||||
closestColorBrightnessDiff = brightnessDiff;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
|
||||
{
|
||||
if (brightnessDiff > closestColorBrightnessDiff)
|
||||
continue;
|
||||
closestColorSGRCode = thisSGRCode;
|
||||
closestColorHueDiff = hueDiff;
|
||||
closestColorSaturationDiff = saturationDiff;
|
||||
closestColorBrightnessDiff = brightnessDiff;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If hue (especially hue!), saturation and brightness diffs all
|
||||
// are equal to some other color, we need to prefer one or the
|
||||
// other so we'll select the more 'distinctive' color of the
|
||||
// two (this is *very* subjective, obviously). I basically just
|
||||
// looked at the hue chart, went through all the points between
|
||||
// our main ANSI colors and decided which side the middle point
|
||||
// would lean on. (e.g. the purple color that is exactly between
|
||||
// the blue and magenta ANSI colors looks more magenta than
|
||||
// blue to me so I put magenta higher than blue in the list
|
||||
// below.)
|
||||
//
|
||||
// subjective ordering of colors from most to least 'distinctive':
|
||||
long colorDistinctivenessOrder[6] = {
|
||||
AMR_SGRCodeFgRed+AMR_SGRCodeShift,
|
||||
AMR_SGRCodeFgMagenta+AMR_SGRCodeShift,
|
||||
AMR_SGRCodeFgBlue+AMR_SGRCodeShift,
|
||||
AMR_SGRCodeFgGreen+AMR_SGRCodeShift,
|
||||
AMR_SGRCodeFgCyan+AMR_SGRCodeShift,
|
||||
AMR_SGRCodeFgYellow+AMR_SGRCodeShift
|
||||
};
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (colorDistinctivenessOrder[i] == closestColorSGRCode)
|
||||
break;
|
||||
else if (colorDistinctivenessOrder[i] == thisSGRCode)
|
||||
{
|
||||
closestColorSGRCode = thisSGRCode;
|
||||
closestColorHueDiff = hueDiff;
|
||||
closestColorSaturationDiff = saturationDiff;
|
||||
closestColorBrightnessDiff = brightnessDiff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestColorSGRCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@ -6,6 +6,7 @@
|
||||
// Copyright © 2018 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AMR_ANSIEscapeHelper.h"
|
||||
#import "TouchBarPrivateApi.h"
|
||||
#import "TouchBarSupport.h"
|
||||
#import "DeprecatedCarbonAPI.h"
|
||||
|
||||
@ -85,6 +85,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
|
||||
if let color = backgroundColor {
|
||||
cell.isBordered = true
|
||||
button.bezelColor = color
|
||||
button.bezelStyle = .rounded
|
||||
cell.backgroundColor = color
|
||||
} else {
|
||||
button.isBordered = isBordered
|
||||
@ -157,6 +158,10 @@ class CustomButtonCell: NSButtonCell {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func drawingRect(forBounds rect: NSRect) -> NSRect {
|
||||
return rect // need that so content may better fit in button with very limited width
|
||||
}
|
||||
|
||||
required init(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
|
||||
@ -343,6 +343,7 @@ class SupportedTypesHolder {
|
||||
enum ItemType: Decodable {
|
||||
case staticButton(title: String)
|
||||
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
|
||||
case timeButton(formatTemplate: String, timeZone: String?)
|
||||
case battery()
|
||||
case dock(autoResize: Bool)
|
||||
@ -387,6 +388,7 @@ enum ItemType: Decodable {
|
||||
enum ItemTypeRaw: String, Decodable {
|
||||
case staticButton
|
||||
case appleScriptTitledButton
|
||||
case shellScriptTitledButton
|
||||
case timeButton
|
||||
case battery
|
||||
case dock
|
||||
@ -413,6 +415,11 @@ enum ItemType: Decodable {
|
||||
let source = try container.decode(Source.self, forKey: .source)
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
||||
self = .appleScriptTitledButton(source: source, refreshInterval: interval)
|
||||
|
||||
case .shellScriptTitledButton:
|
||||
let source = try container.decode(Source.self, forKey: .source)
|
||||
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
|
||||
self = .shellScriptTitledButton(source: source, refreshInterval: interval)
|
||||
|
||||
case .staticButton:
|
||||
let title = try container.decode(String.self, forKey: .title)
|
||||
|
||||
74
MTMR/ShellScriptTouchBarItem.swift
Normal file
74
MTMR/ShellScriptTouchBarItem.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// ShellScriptTouchBarItem.swift
|
||||
// MTMR
|
||||
//
|
||||
// Created by bobr on 08/08/2019.
|
||||
// Copyright © 2019 Anton Palgunov. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
|
||||
private let interval: TimeInterval
|
||||
private let source: String
|
||||
private var forceHideConstraint: NSLayoutConstraint!
|
||||
|
||||
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
|
||||
self.interval = interval
|
||||
self.source = source.string ?? "echo No \"source\""
|
||||
super.init(identifier: identifier, title: "⏳")
|
||||
|
||||
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
|
||||
|
||||
DispatchQueue.shellScriptQueue.async {
|
||||
self.refreshAndSchedule()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func refreshAndSchedule() {
|
||||
// Execute script and get result
|
||||
let scriptResult = execute(source)
|
||||
|
||||
// Apply returned text attributes (if they were returned) to our result string
|
||||
let helper = AMR_ANSIEscapeHelper.init()
|
||||
helper.defaultStringColor = NSColor.white
|
||||
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
|
||||
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: scriptResult) ?? NSAttributedString(string: ""))
|
||||
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
|
||||
let newBackgoundColor = title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor
|
||||
|
||||
// Update UI
|
||||
DispatchQueue.main.async { [weak self, newBackgoundColor] in
|
||||
self?.backgroundColor = newBackgoundColor
|
||||
self?.attributedTitle = title
|
||||
self?.forceHideConstraint.isActive = scriptResult == ""
|
||||
}
|
||||
|
||||
// Schedule next update
|
||||
DispatchQueue.shellScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
|
||||
self?.refreshAndSchedule()
|
||||
}
|
||||
}
|
||||
|
||||
func execute(_ command: String) -> String {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
task.arguments = ["-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
|
||||
|
||||
return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression)
|
||||
}
|
||||
}
|
||||
|
||||
extension DispatchQueue {
|
||||
static let shellScriptQueue = DispatchQueue(label: "mtmr.shellscript")
|
||||
}
|
||||
@ -23,6 +23,8 @@ extension ItemType {
|
||||
return "com.toxblh.mtmr.staticButton."
|
||||
case .appleScriptTitledButton(source: _):
|
||||
return "com.toxblh.mtmr.appleScriptButton."
|
||||
case .shellScriptTitledButton(source: _):
|
||||
return "com.toxblh.mtmr.shellScriptButton."
|
||||
case .timeButton(formatTemplate: _, timeZone: _):
|
||||
return "com.toxblh.mtmr.timeButton."
|
||||
case .battery():
|
||||
@ -251,6 +253,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
|
||||
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
|
||||
case let .appleScriptTitledButton(source: source, refreshInterval: interval):
|
||||
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
||||
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
|
||||
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
|
||||
case let .timeButton(formatTemplate: template, timeZone: timeZone):
|
||||
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone)
|
||||
case .battery():
|
||||
|
||||
@ -12,7 +12,7 @@ import CoreLocation
|
||||
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
|
||||
private let activity: NSBackgroundActivityScheduler
|
||||
private let unitsStr = "°C"
|
||||
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
|
||||
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "⛈", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
|
||||
private var location: CLLocation!
|
||||
private var prevLocation: CLLocation!
|
||||
private var manager: CLLocationManager!
|
||||
|
||||
64
README.md
64
README.md
@ -98,6 +98,13 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
- sleep
|
||||
- displaySleep
|
||||
|
||||
> Custom buttons
|
||||
|
||||
- staticButton
|
||||
- appleScriptTitledButton
|
||||
- shellScriptTitledButton
|
||||
- timeButton
|
||||
|
||||
## Gestures on central part:
|
||||
|
||||
- two finger slide: change you Volume
|
||||
@ -110,16 +117,17 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
|
||||
### You can also make custom buttons using these types
|
||||
|
||||
- `staticButton`
|
||||
#### `staticButton`
|
||||
|
||||
```json
|
||||
"type": "staticButton",
|
||||
"title": "esc",
|
||||
```
|
||||
|
||||
- `appleScriptTitledButton`
|
||||
#### `appleScriptTitledButton`
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "appleScriptTitledButton",
|
||||
"refreshInterval": 60, //optional
|
||||
"source": {
|
||||
@ -128,26 +136,58 @@ The pre-installed configuration contains less or more than you'll probably want,
|
||||
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell",
|
||||
// or
|
||||
"base64": "StringInbase64"
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `timeButton`
|
||||
#### `shellScriptTitledButton`
|
||||
> Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting)
|
||||
> Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color.
|
||||
|
||||
Example of "CPU load" button which also changes color based on load value.
|
||||
```js
|
||||
{
|
||||
"type": "shellScriptTitledButton",
|
||||
"width": 80,
|
||||
"refreshInterval": 2,
|
||||
"source": {
|
||||
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
|
||||
},
|
||||
"action": "appleScript",
|
||||
"actionAppleScript": {
|
||||
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
|
||||
},
|
||||
"align": "right",
|
||||
"image": {
|
||||
"base64":
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
|
||||
},
|
||||
"bordered": false
|
||||
}
|
||||
```
|
||||
|
||||
#### `timeButton`
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "timeButton",
|
||||
"formatTemplate": "HH:mm" //optional
|
||||
}
|
||||
```
|
||||
|
||||
## Groups
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "group",
|
||||
"align": "center",
|
||||
"bordered": true,
|
||||
"title": "stats",
|
||||
"items": [
|
||||
{ "type": "play" }, { "type": "mute" }, ...]
|
||||
"type": "group",
|
||||
"align": "center",
|
||||
"bordered": true,
|
||||
"title": "stats",
|
||||
"items": [
|
||||
{ "type": "play" },
|
||||
{ "type": "mute" },
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@ -155,8 +195,8 @@ To close a group, use the button:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "close",
|
||||
"width": 64
|
||||
"type": "close",
|
||||
"width": 64
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user