Xcode-style placeholders in an NSTextView

2019-03-30 06:22发布

问题:

In Xcode, if you type <# Hello, Word #> into the text editor, it automatically gets converted to a pale-blue pill-shaped placeholder, but on disk, the text remains exactly as it was typed. Does anyone know if the same effect is achievable using NSTextView? I've got some very ugly filepaths that must remain exactly as they are so sphinx can put together my docs, but I want to present the user with something a little more attractive when they view the file in my custom text editor.

// This on disk (and in any other text editor)
.. image:: images/ssafs/sdfd-sdfsdg-ewfsdf.png

// This shown to the user in my custom text editor
Image of a golden eagle

回答1:

As much as possible tried to write explanations as a comment in code. What I have done here.

  1. Found all matched links .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf1.png with Regex and add them into an array.
  2. Replaced all matched links .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf1.png with [Image] string
  3. Found all [Image] strings and formatted with NSMutableAttributedString as a link.

It does what you are asking and it does on the fly, your source in the database/file does not change at all.

.h

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate, NSTextViewDelegate>
@property (unsafe_unretained) IBOutlet NSTextView *aTextView;

.m

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    //Your NSTextView
    [aTextView setDelegate:(id)self];

    // The Context
    NSString *string = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec convallis .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf1.png lacinia diam, in mattis quam egestas in. Nam gravida dolor adipiscing velit faucibus, vulputate facilisis diam facilisis. Duis id magna nibh. Proin sed turpis aliquet .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf2.png, posuere purus eget, condimentum nulla. Aenean erat odio, suscipit eu aliquet eget, porta in justo. Quisque sed sem dignissim, luctus .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf3.png libero ut, congue libero. Curabitur tristique fermentum risus in fermentum.";

    //Regex to find your links .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf2.png
    //You can / should improve Reges patter.
    NSRegularExpression *regexPatternForFullLinks = [NSRegularExpression regularExpressionWithPattern:@"(\\.\\.\\s(.*?\\.png))"
                                                                                              options:NSRegularExpressionCaseInsensitive error:nil];
    //Here find all image links and add them into an Array
    NSArray *arrayOfAllMatches = [regexPatternForFullLinks matchesInString:string options:0 range:NSMakeRange(0, string.length)];
    NSMutableArray *links = [[NSMutableArray alloc] init];
    for (NSTextCheckingResult *match in arrayOfAllMatches) {
        [links addObject:[[string substringWithRange:match.range] stringByReplacingOccurrencesOfString:@".. image:: " withString:@"/"]];
    }

    //Replacing All your links with string: [Image]
    NSString *modifiedString = [regexPatternForFullLinks stringByReplacingMatchesInString:string
                                                                                  options:0
                                                                                    range:NSMakeRange(0, [string length])
                                                                             withTemplate:@"[Image]"];

    NSRegularExpression *regexPatternReplaceLinksWithIMAGEStr = [NSRegularExpression regularExpressionWithPattern:@"\\[image\\]"
                                                                                              options:NSRegularExpressionCaseInsensitive error:nil];

    NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString:modifiedString];

    //Here,looking for all [Image] strings and add them Link Attribute
    NSArray *arrayOfAllMatchesImageText = [regexPatternReplaceLinksWithIMAGEStr matchesInString:modifiedString
                                                                                        options:0
                                                                                          range:NSMakeRange(0, modifiedString.length)];

    for (int i = 0; i < arrayOfAllMatchesImageText.count; i++) {
        NSTextCheckingResult *checkingResult = [arrayOfAllMatchesImageText objectAtIndex:i];
        [attrString beginEditing];
        [attrString addAttribute:NSLinkAttributeName value:[links objectAtIndex:i] range:checkingResult.range];
        [attrString addAttribute:NSForegroundColorAttributeName value:[NSColor greenColor] range:checkingResult.range];
        [attrString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:0] range:checkingResult.range];
        [attrString endEditing];
    }

    //Set NSTextView Storage text...
    [aTextView.textStorage setAttributedString:attrString];
}

NSTextViewDelegate: clickedOnLink to handle link clicks.

//Open Given Links with Preview App - NSTextViewDelegate 
- (BOOL)textView:(NSTextView *)aTextView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex {
    [[NSWorkspace sharedWorkspace] openFile:link withApplication:@"Preview"];
    NSLog(@"%@", link);
    return YES;
}

You can see final result on the image. If you want you can give background color to links as well.

NSTextView settings are important as well.

Update:

Just figure out this can be done more elegantly and it is efficient (faster).

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    //Your NSTextView
    [aTextView setDelegate:(id)self];

    // The Context
    NSString *string = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec convallis .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf1.png lacinia diam, in mattis quam egestas in. Nam gravida dolor adipiscing velit faucibus, vulputate facilisis diam facilisis. Duis id magna nibh. Proin sed turpis aliquet .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf2.png, posuere purus eget, condimentum nulla. Aenean erat odio, suscipit eu aliquet eget, porta in justo. Quisque sed sem dignissim, luctus .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf3.png libero ut, congue libero. Curabitur tristique fermentum risus in fermentum.";

    //Regex to find your links .. image:: images/ssafs/sdfd-sdfsdg-ewfsdf2.png
    //You can / should improve Reges patter.
    NSRegularExpression *regexPatternForFullLinks = [NSRegularExpression regularExpressionWithPattern:@"(\\.\\.\\s(.*?\\.png))"
                                                                                              options:NSRegularExpressionCaseInsensitive error:nil];
    //Here find all image links and add them into an Array
    NSArray *arrayOfAllMatches = [regexPatternForFullLinks matchesInString:string options:0 range:NSMakeRange(0, string.length)];

    __block NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString:string];

    [arrayOfAllMatches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSTextCheckingResult *match = [arrayOfAllMatches objectAtIndex:idx];
        NSString *linkValue = [[string substringWithRange:match.range] stringByReplacingOccurrencesOfString:@".. image:: " withString:@"/"];
        NSDictionary *linkAttributes = @{NSForegroundColorAttributeName: [NSColor greenColor],
                                         NSUnderlineStyleAttributeName: [NSNumber numberWithInt:0],
                                         NSLinkAttributeName:linkValue};

        NSMutableAttributedString *tempAttrString = [[NSMutableAttributedString alloc] initWithString:@"[Image]" attributes:linkAttributes];
        [attrString beginEditing];
        [attrString replaceCharactersInRange:match.range withAttributedString:tempAttrString];
        [attrString endEditing];
    }];
    [aTextView.textStorage setAttributedString:attrString];
}


回答2:

I think you can do this sort of thing with NSTextAttachment and a custom subclass of NSTextAttachmentCell.

Basically create an NSAttributedString with your and NSTextAtachment that is using your custom NSTextAttachmentCell

Source code examples:

from the BGHUDAppKit framework

and an extension of that class for colorization from e.printstacktrace()

Edit: it looks like the NSTokenAttachmentCell from BGHUD is actually a private apple class. so you could just use that directly if you dont need to be AppStore compliant.