@@ -223,7 +223,7 @@ @implementation MMCoreTextView {
223223 int cmdlineRow; // /< Row number (0-indexed) where the cmdline starts. Used for pinning it to the bottom if desired.
224224}
225225
226- - (id )initWithFrame : (NSRect )frame
226+ - (instancetype )initWithFrame : (NSRect )frame
227227{
228228 if (!(self = [super initWithFrame: frame]))
229229 return nil ;
@@ -1597,6 +1597,12 @@ - (NSUInteger)characterIndexForPoint:(NSPoint)point
15971597 return utfCharIndexFromRowCol (&grid, row, col);
15981598}
15991599
1600+ // / Returns the cursor location in the text storage. Note that the API is
1601+ // / supposed to return a range if there are selected texts, but since we don't
1602+ // / have access to the full text storage in MacVim (it requires IPC calls to
1603+ // / Vim), we just return the cursor with the range always having zero length.
1604+ // / This affects the quickLookWithEvent: implementation where we have to
1605+ // / manually handle the selected text case.
16001606- (NSRange )selectedRange
16011607{
16021608 if ([helper hasMarkedText ]) {
@@ -1667,8 +1673,152 @@ - (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRang
16671673 }
16681674}
16691675
1676+ // / Optional function in text input client. Returns the proper baseline delta
1677+ // / for the returned rect. We need to do this because we take the ceil() of
1678+ // / fontDescent, which subtly changes the baseline relative to what the OS thinks,
1679+ // / and would have resulted in a slightly offset text under certain fonts/sizes.
1680+ - (CGFloat)baselineDeltaForCharacterAtIndex : (NSUInteger )anIndex
1681+ {
1682+ // Note that this function is calculated top-down, so we need to subtract from height.
1683+ return cellSize.height - fontDescent;
1684+ }
1685+
16701686#pragma endregion // Text Input Client
16711687
1688+ // / Perform data lookup. This gets called by the OS when the user uses
1689+ // / Ctrl-Cmd-D or the trackpad to look up data.
1690+ // /
1691+ // / This implementation will default to using the OS's implementation,
1692+ // / but also perform special checking for selected text, and perform data
1693+ // / detection for URLs, etc.
1694+ - (void )quickLookWithEvent : (NSEvent *)event
1695+ {
1696+ // The default implementation would query using the NSTextInputClient API
1697+ // which works fine.
1698+ //
1699+ // However, by default, if there are texts that are selected, *and* the
1700+ // user performs lookup when the mouse is on top of said selected text, the
1701+ // OS will use that for the lookup instead. E.g. if the user has selected
1702+ // "ice cream" and perform a lookup on it, the lookup will be "ice cream"
1703+ // instead of "ice" or "cream". We need to implement this in a custom
1704+ // fashion because our `selectedRange` implementation doesn't properly
1705+ // return the selected text (which we cannot do easily since our text
1706+ // storage isn't representative of the Vim's internal buffer, see above
1707+ // design notes), by querying Vim for the selected text manually.
1708+ //
1709+ // Another custom implementation we do is by first feeding the data through
1710+ // an NSDataDetector first. This helps us catch URLs, addresses, and so on.
1711+ // Otherwise for an URL, it will not include the whole https:// part and
1712+ // won't show a web page. Note that NSTextView/WebKit/etc all use an
1713+ // internal API called Reveal which does this for free and more powerful,
1714+ // but we don't have access to that as a third-party software that
1715+ // implements a custom text view.
1716+
1717+ const NSPoint pt = [self convertPoint: [event locationInWindow ] fromView: nil ];
1718+ int row = 0 , col = 0 ;
1719+ if ([self convertPoint: pt toRow: &row column: &col]) {
1720+ // 1. If we have selected text. Proceed to see if the mouse is directly on
1721+ // top of said selection and if so, show definition of that instead.
1722+ MMVimController *vc = [self vimController ];
1723+ id <MMBackendProtocol> backendProxy = [vc backendProxy ];
1724+ if ([backendProxy selectedTextToPasteboard: nil ]) {
1725+ int selRow = 0 , selCol = 0 ;
1726+ const BOOL isMouseInSelection = [backendProxy mouseScreenposIsSelection: row column: col selRow: &selRow selCol: &selCol];
1727+
1728+ if (isMouseInSelection) {
1729+ NSString *selectedText = [backendProxy selectedText ];
1730+ if (selectedText) {
1731+ NSAttributedString *attrText = [[[NSAttributedString alloc ] initWithString: selectedText
1732+ attributes: @{NSFontAttributeName : font}
1733+ ] autorelease ];
1734+
1735+ const NSRect selRect = [self rectForRow: selRow
1736+ column: selCol
1737+ numRows: 1
1738+ numColumns: 1 ];
1739+
1740+ NSPoint baselinePt = selRect.origin ;
1741+ baselinePt.y += fontDescent;
1742+
1743+ // We have everything we need. Just show the definition and return.
1744+ [self showDefinitionForAttributedString: attrText atPoint: baselinePt];
1745+ return ;
1746+ }
1747+ }
1748+ }
1749+
1750+ // 2. Check if we have specialized data. Honestly the OS should really do this
1751+ // for us as we are just calling text input client APIs here.
1752+ const NSUInteger charIndex = utfCharIndexFromRowCol (&grid, row, col);
1753+ NSTextCheckingTypes checkingTypes = NSTextCheckingTypeAddress
1754+ | NSTextCheckingTypeLink
1755+ | NSTextCheckingTypePhoneNumber;
1756+ // | NSTextCheckingTypeDate // Date doesn't really work for showDefinition without private APIs
1757+ // | NSTextCheckingTypeTransitInformation // Flight info also doesn't work without private APIs
1758+ NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes: checkingTypes error: nil ];
1759+ if (detector != nil ) {
1760+ // Just check [-100,100) around the mouse cursor. That should be more than enough to find interesting information.
1761+ const NSUInteger rangeSize = 100 ;
1762+ const NSUInteger rangeOffset = charIndex > rangeSize ? rangeSize : charIndex;
1763+ const NSRange checkRange = NSMakeRange (charIndex - rangeOffset, charIndex + rangeSize * 2 );
1764+
1765+ NSAttributedString *attrStr = [self attributedSubstringForProposedRange: checkRange actualRange: nil ];
1766+
1767+ __block NSUInteger count = 0 ;
1768+ __block NSRange foundRange = NSMakeRange (0 , 0 );
1769+ [detector enumerateMatchesInString: attrStr.string
1770+ options: 0
1771+ range: NSMakeRange (0 , attrStr.length)
1772+ usingBlock: ^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
1773+ if (++count >= 30 ) {
1774+ // Sanity checking
1775+ *stop = YES ;
1776+ }
1777+
1778+ NSRange matchRange = [match range ];
1779+ if (!NSLocationInRange (rangeOffset, matchRange)) {
1780+ // We found something interesting nearby, but it's not where the mouse cursor is, just move on.
1781+ return ;
1782+ }
1783+ if (match.resultType == NSTextCheckingTypeLink) {
1784+ foundRange = matchRange;
1785+ *stop = YES ; // URL is highest priority, so we always terminate.
1786+ } else if (match.resultType == NSTextCheckingTypePhoneNumber || match.resultType == NSTextCheckingTypeAddress) {
1787+ foundRange = matchRange;
1788+ }
1789+ }];
1790+
1791+ if (foundRange.length != 0 ) {
1792+ // We found something interesting! Show that instead of going through the default OS behavior.
1793+ NSUInteger startIndex = charIndex + foundRange.location - rangeOffset;
1794+
1795+ int row = 0 , col = 0 , firstLineNumCols = 0 , firstLineUtf8Len = 0 ;
1796+ rowColFromUtfRange (&grid, NSMakeRange (startIndex, 0 ), &row, &col, &firstLineNumCols, &firstLineUtf8Len);
1797+ const NSRect rectToShow = [self rectForRow: row
1798+ column: col
1799+ numRows: 1
1800+ numColumns: 1 ];
1801+
1802+ NSPoint baselinePt = rectToShow.origin ;
1803+ baselinePt.y += fontDescent;
1804+
1805+ [self showDefinitionForAttributedString: attrStr
1806+ range: foundRange
1807+ options: @{}
1808+ baselineOriginProvider: ^NSPoint (NSRange adjustedRange) {
1809+ return baselinePt;
1810+ }];
1811+ return ;
1812+ }
1813+ }
1814+ }
1815+
1816+ // Just call the default implementation, which will call misc
1817+ // NSTextInputClient methods on us and use that to determine what/where to
1818+ // show.
1819+ [super quickLookWithEvent: event];
1820+ }
1821+
16721822@end // MMCoreTextView
16731823
16741824
0 commit comments