louisdh / openterm

OpenTerm, a sandboxed command line interface for iOS
Other
1.63k stars 248 forks source link

Suggestion: Better xterm compatibility #5

Open holzschu opened 6 years ago

holzschu commented 6 years ago

Would it be possible to have the terminal window emulate a xterm? With control caracters to change font color, bold, etc?

Try "curl -s wttr.in/london" in a MacOS Terminal window and in Terminal so see the effects.

holzschu commented 6 years ago

This could probably be done by using a WebView instead of the TextView, and combining with xterm.js https://github.com/xtermjs/xterm.js?files=1 Seems like a big change, though.

tbodt commented 6 years ago

You can look at my app for how to do this: https://github.com/tbodt/ish/blob/master/app/TerminalView.m https://github.com/tbodt/ish/tree/master/app/xtermjs

palmin commented 6 years ago

I have some objective-c code for converting a NSString into a NSAttributtedString where some ANSI escape sequences are interpreted. Avoiding a trip through a web-context might be worth considering.

You can get really far by supporting foreground & background colours sequences and ignoring all other escape sequences.

ColdGrub1384 commented 6 years ago

@tbodt Yes, I'm also using xterm.js, it's very useful and easy to implement

ian-mcdowell commented 6 years ago

I think NSAttributedString & CoreText is probably the best approach for this app. I'd like to not have to deal with WKWebView and its own sandboxing & file I/O issues if possible.

@palmin Where can I find this code? I'd like to Swift-ify it.. 🙂

palmin commented 6 years ago

Very simple and only supports the most basic foreground/background color commands, but it should be easy to adjustAnsiTextState to support additional commands.

@interface AnsiTextState : NSObject {
    UIColor* foregroundColor;
    UIColor* backgroundColor;
}

-(NSUInteger)parseRange:(NSRange)range text:(NSString*)text;
-(NSDictionary*)attributes;

@end

@implementation AnsiTextState

-(void)reset {
    foregroundColor = nil;
    backgroundColor = nil;
}

-(UIColor*)colorWithIndex:(int)index {
    if(index == 0) return [UIColor blackColor];
    if(index == 1) return [UIColor redColor];
    if(index == 2) return [UIColor greenColor];
    if(index == 3) return [UIColor yellowColor];
    if(index == 4) return [UIColor blueColor];
    if(index == 5) return [UIColor magentaColor];
    if(index == 6) return [UIColor cyanColor];
    if(index == 7) return [UIColor whiteColor];

    return nil;
}

-(void)setForegroundColor:(int)index {
    foregroundColor = [self colorWithIndex:index];
}

-(void)setBackgroundColor:(int)index {
    backgroundColor = [self colorWithIndex:index];
}

-(NSUInteger)parseRange:(NSRange)range text:(NSString*)text {
    NSUInteger pos = range.location, end = range.location + range.length;
    while(pos < end) {
        // Ends at ASCII 64 to 126 (@ to ~ / hex 0x40 to 0x7E)
        unichar ch = [text characterAtIndex:pos];
        if(ch >= 64 && ch <= 126) {
            // we have start and end and run through updating state
            NSString* payload = [text substringWithRange:NSMakeRange(range.location, pos - range.location)];
            NSArray<NSString*>* commands = [payload componentsSeparatedByString:@";"];
            for (NSString* command in commands) {
                int cmd = [command intValue];

                if(cmd == 0) [self reset];
                if(cmd >= 30 && cmd <= 37) [self setForegroundColor: cmd-30];
                if(cmd >= 40 && cmd <= 47) [self setBackgroundColor: cmd-40];
            }

            return pos+1;
        }

        pos += 1;
    }
    return end;
}

-(NSDictionary*)attributes {
    NSMutableDictionary* attr = [NSMutableDictionary new];
    if(foregroundColor != nil) attr[NSForegroundColorAttributeName] = foregroundColor;
    if(backgroundColor != nil) attr[NSBackgroundColorAttributeName] = backgroundColor;
    return attr;
}

@end

@implementation NSString (AnsiEscapeSequence)

-(NSAttributedString*)interpretedEscapeSequences {
    NSMutableAttributedString* string = [NSMutableAttributedString new];

    AnsiTextState* state = [AnsiTextState new];
    NSUInteger pos = 0, len = self.length;
    while(pos < len) {
        // skip forward to next escape sequence
        NSUInteger next = [self rangeOfString:@"\e[" options:0 range:NSMakeRange(pos, len-pos)].location;
        if(next == NSNotFound) next = len;
        if(pos < next) {
            NSString* substr = [self substringWithRange:NSMakeRange(pos, next - pos)];
            NSAttributedString* substring = [[NSAttributedString alloc] initWithString:substr
                                                                            attributes:state.attributes];
            [string appendAttributedString: substring];
        }

        // parse escape sequence and adjust state
        NSUInteger done = [state parseRange: NSMakeRange(next+2, len-next-2) text:self];
        pos = done;
    }
    return string;
}

@end
palmin commented 6 years ago

Maybe it would make sense to disable the ansi color commands when using a non-standard background color in the app or perhaps just to have a warning where you change the background color?

Most of these ansi color coded outputs are only readable if you have a dark background or if the colors are adjusted to match a non-standard background color.

louisdh commented 6 years ago

This issue is being (partially?) addressed by #62, btw.

louisdh commented 6 years ago

@palmin Good point. I noticed Terminal on macOS lets users change ANSI colors, which solves background color issues. Not sure if we should go that far, though.

palmin commented 6 years ago

Yes. The escape sequence color/style stuff seems to happen in #62 which seems like a good idea as this issue here is very broad.

ian-mcdowell commented 6 years ago

More work on this in #85. We can support curl progress bars now!