PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/TmuxHistoryParser.m

https://github.com/rcg4u/iTerm2
Objective C | 277 lines | 218 code | 22 blank | 37 comment | 65 complexity | b172603e971558ce9e9ce5440439a025 MD5 | raw file
  1. //
  2. // TmuxHistoryParser.m
  3. // iTerm
  4. //
  5. // Created by George Nachman on 11/29/11.
  6. //
  7. #import "TmuxHistoryParser.h"
  8. #import "ScreenChar.h"
  9. typedef struct {
  10. int attr;
  11. int flags;
  12. int fg;
  13. int bg;
  14. screen_char_t prototype;
  15. BOOL isDwcPadding;
  16. } HistoryParseContext;
  17. // -- begin section copied from tmux.h --
  18. /* Grid attributes. */
  19. #define GRID_ATTR_BRIGHT 0x1
  20. #define GRID_ATTR_DIM 0x2
  21. #define GRID_ATTR_UNDERSCORE 0x4
  22. #define GRID_ATTR_BLINK 0x8
  23. #define GRID_ATTR_REVERSE 0x10
  24. #define GRID_ATTR_HIDDEN 0x20
  25. #define GRID_ATTR_ITALICS 0x40
  26. #define GRID_ATTR_CHARSET 0x80 /* alternative character set */
  27. /* Grid flags. */
  28. #define GRID_FLAG_FG256 0x1
  29. #define GRID_FLAG_BG256 0x2
  30. #define GRID_FLAG_PADDING 0x4
  31. #define GRID_FLAG_UTF8 0x8
  32. /* Grid line flags. */
  33. #define GRID_LINE_WRAPPED 0x1
  34. // -- end section copied from tmux.h --
  35. @implementation TmuxHistoryParser
  36. + (TmuxHistoryParser *)sharedInstance
  37. {
  38. static TmuxHistoryParser *instance;
  39. if (!instance) {
  40. instance = [[TmuxHistoryParser alloc] init];
  41. }
  42. return instance;
  43. }
  44. - (screen_char_t)prototypeScreenCharWithAttributes:(int)attributes
  45. flags:(int)flags
  46. fg:(int)fg
  47. bg:(int)bg
  48. {
  49. screen_char_t temp;
  50. memset(&temp, 0, sizeof(temp));
  51. if (fg == 8) {
  52. // Default fg
  53. temp.foregroundColor = ALTSEM_FG_DEFAULT;
  54. temp.alternateForegroundSemantics = YES;
  55. } else {
  56. temp.foregroundColor = fg;
  57. temp.alternateForegroundSemantics = NO;
  58. // TODO: GRID_ATTR_DIM not supported
  59. }
  60. if (bg == 8) {
  61. temp.backgroundColor = ALTSEM_BG_DEFAULT;
  62. temp.alternateBackgroundSemantics = YES;
  63. } else {
  64. temp.backgroundColor = bg;
  65. temp.alternateBackgroundSemantics = NO;
  66. }
  67. if (attributes & GRID_ATTR_BRIGHT) {
  68. temp.bold = YES;
  69. }
  70. if (attributes & GRID_ATTR_UNDERSCORE) {
  71. temp.underline = YES;
  72. }
  73. if (attributes & GRID_ATTR_BLINK) {
  74. temp.blink = YES;
  75. }
  76. if (attributes & GRID_ATTR_REVERSE) {
  77. int x = temp.foregroundColor;
  78. temp.foregroundColor = temp.backgroundColor;
  79. temp.backgroundColor = x;
  80. x = temp.alternateForegroundSemantics;
  81. temp.alternateForegroundSemantics = temp.alternateBackgroundSemantics;
  82. temp.alternateBackgroundSemantics = x;
  83. }
  84. if (attributes & GRID_ATTR_HIDDEN) {
  85. // TODO not supported (SGR 8)
  86. }
  87. if (attributes & GRID_ATTR_ITALICS) {
  88. // TODO not supported
  89. }
  90. if (attributes & GRID_ATTR_CHARSET) {
  91. // TODO not supported
  92. }
  93. return temp;
  94. }
  95. // Convert a hex digit at s into an int placing it in *out and returning the number
  96. // of characters used in the conversion.
  97. static int consume_hex(const char *s, int *out)
  98. {
  99. char const *endptr = s;
  100. *out = strtol(s, (char**) &endptr, 16);
  101. return endptr - s;
  102. }
  103. - (NSData *)dataForHistoryLine:(NSString *)hist
  104. withContext:(HistoryParseContext *)ctx
  105. {
  106. NSMutableData *result = [NSMutableData data];
  107. screen_char_t lastChar;
  108. BOOL softEol = NO;
  109. if ([hist hasSuffix:@"+"]) {
  110. softEol = YES;
  111. hist = [hist substringWithRange:NSMakeRange(0, hist.length - 1)];
  112. }
  113. const char *s = [hist UTF8String];
  114. for (int i = 0; s[i]; ) {
  115. if (s[i] == ':') {
  116. // NSLog(@"found a : at %d", i);
  117. // Context update follows
  118. i++;
  119. int values[4];
  120. for (int j = 0; j < 4; j++) {
  121. int n = consume_hex(s + i, &values[j]);
  122. i += n;
  123. if (s[i] == ',') {
  124. i++;
  125. } else {
  126. return nil;
  127. }
  128. }
  129. ctx->prototype = [self prototypeScreenCharWithAttributes:values[0]
  130. flags:values[1]
  131. fg:values[2]
  132. bg:values[3]];
  133. // attr, flags, fg, bg
  134. if (values[1] & GRID_FLAG_PADDING) {
  135. ctx->isDwcPadding = YES;
  136. } else {
  137. ctx->isDwcPadding = NO;
  138. }
  139. } else if (s[i] == '*') {
  140. // NSLog(@"found a * at %d", i);
  141. i++;
  142. NSInteger repeats;
  143. // We have a "*<number> " sequence. Scan the number.
  144. if ([[NSScanner scannerWithString:[NSString stringWithUTF8String:s + i]] scanInteger:&repeats]) {
  145. // Append the last character repeats-1 times.
  146. for (int j = 0; j < repeats - 1; j++) {
  147. [result appendBytes:&lastChar length:sizeof(screen_char_t)];
  148. }
  149. // Advance up to and then past the terminal space, if present.
  150. while (s[i] && s[i] != ' ') {
  151. i++;
  152. }
  153. if (s[i] == ' ') {
  154. i++;
  155. } else {
  156. NSLog(@"malformed dump history lacks a space after *n: <<%@>>", hist);
  157. return nil;
  158. }
  159. } else {
  160. NSLog(@"malformed dump history lacks a number after *: <<%@>>", hist);
  161. return nil;
  162. }
  163. }
  164. // array of 2-digit hex values interspersed with [ 2 digit hex values ].
  165. BOOL utf8 = NO;
  166. NSMutableData *utf8Buffer = [NSMutableData data];
  167. while (s[i] == '[' ||
  168. s[i] == ']' ||
  169. (ishexnumber(s[i]) && ishexnumber(s[i + 1]))) {
  170. // NSLog(@"top of while loop: i=%d", i);
  171. if (s[i] == '[') {
  172. // NSLog(@"-- begin utf 8 --");
  173. if (s[i+1] && s[i+2] && s[i+3]) {
  174. utf8 = YES;
  175. [utf8Buffer setLength:0];
  176. } else {
  177. NSLog(@"Malformed text after [ in history");
  178. return nil;
  179. }
  180. i++;
  181. } else if (s[i] == ']') {
  182. // NSLog(@"-- end utf 8 --");
  183. if (utf8) {
  184. utf8 = NO;
  185. NSString *stringValue = [[[NSString alloc] initWithData:utf8Buffer encoding:NSUTF8StringEncoding] autorelease];
  186. ctx->prototype.code = GetOrSetComplexChar(stringValue);
  187. ctx->prototype.complexChar = 1;
  188. lastChar = ctx->prototype;
  189. [result appendBytes:&ctx->prototype length:sizeof(screen_char_t)];
  190. } else {
  191. NSLog(@"] without [ in history");
  192. return nil;
  193. }
  194. i++;
  195. continue;
  196. } else {
  197. // Read a hex digit
  198. unsigned scanned;
  199. if ([[NSScanner scannerWithString:[NSString stringWithFormat:@"%c%c", s[i], s[i+1]]] scanHexInt:&scanned]) {
  200. // NSLog(@"scanned %@", [NSString stringWithFormat:@"%c%c", s[i], s[i+1]]);
  201. if (utf8) {
  202. char c = scanned;
  203. [utf8Buffer appendBytes:&c length:1];
  204. } else {
  205. if (ctx->isDwcPadding) {
  206. ctx->prototype.code = DWC_RIGHT;
  207. } else {
  208. ctx->prototype.code = scanned;
  209. }
  210. ctx->prototype.complexChar = 0;
  211. // Skip DWC_RIGHT if it's the first thing in a line. It would
  212. // be better to set the last char of the previous line to DWC_SKIP
  213. // and the eol to EOF_DWC, but I think tmux prevents this from
  214. // happening anyway.
  215. if (result.length > 0 || ctx->prototype.code != DWC_RIGHT) {
  216. lastChar = ctx->prototype;
  217. [result appendBytes:&ctx->prototype length:sizeof(screen_char_t)];
  218. }
  219. }
  220. i += 2;
  221. } else {
  222. NSLog(@"Malformed hex array at %d: \"%c%c\" (%d %d)", i, s[i], s[i+1], (int) s[i], (int) s[i+1]);
  223. return nil;
  224. }
  225. }
  226. }
  227. if (utf8) {
  228. NSLog(@"Malformed history line has unclosed utf8 at %d: %@", i, hist);
  229. return nil;
  230. }
  231. }
  232. screen_char_t eolSct;
  233. if (softEol) {
  234. eolSct.code = EOL_SOFT;
  235. } else {
  236. eolSct.code = EOL_HARD;
  237. }
  238. [result appendBytes:&eolSct length:sizeof(eolSct)];
  239. return result;
  240. }
  241. // Return an NSArray of NSData's. Each NSData is an array of screen_char_t's,
  242. // with the last element in each being the newline.
  243. - (NSArray *)parseDumpHistoryResponse:(NSString *)response
  244. {
  245. NSArray *lines = [response componentsSeparatedByString:@"\n"];
  246. NSMutableArray *screenLines = [NSMutableArray array];
  247. HistoryParseContext ctx;
  248. memset(&ctx, 0, sizeof(ctx));
  249. for (NSString *line in lines) {
  250. [screenLines addObject:[self dataForHistoryLine:line
  251. withContext:&ctx]];
  252. }
  253. return screenLines;
  254. }
  255. @end