mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-06-07 15:37:14 +02:00
Merge pull request #1312 from ychin/text-input-client-support-lookup
Support dictionary/data lookups of text
This commit is contained in:
+10
-2
@@ -259,6 +259,8 @@ as general information regarding macOS user defaults.
|
||||
Here is a list of relevant dictionary entries:
|
||||
|
||||
KEY VALUE ~
|
||||
*MMAllowForceClickLookUp* use Force click for data lookup instead of
|
||||
<ForceClick> [bool]
|
||||
*MMCellWidthMultiplier* width of a normal glyph in em units [float]
|
||||
*MMCmdLineAlignBottom* Pin command-line to bottom of MacVim [bool]
|
||||
*MMDialogsTrackPwd* open/save dialogs track the Vim pwd [bool]
|
||||
@@ -783,11 +785,17 @@ Each gesture generates one of the following Vim pseudo keys:
|
||||
|
||||
*<SwipeUp>* *<SwipeDown>*
|
||||
Generated when swiping three fingers across the trackpad in a
|
||||
vertical direction. (Not supported by the Apple Magic Mouse.)
|
||||
vertical direction. (Not supported by the Apple Magic Mouse)
|
||||
|
||||
*<ForceClick>*
|
||||
Generated when doing a Force click by pressing hard on a trackpad.
|
||||
(Only supported on trackpads that support Force Touch.)
|
||||
(Only supported on trackpads that support Force Touch)
|
||||
|
||||
If you have configured to use Force click for "Look up & data
|
||||
detectors" in the system settings, by default MacVim will do a
|
||||
dictionary lookup instead of triggering this mapping. You can turn
|
||||
this off in MacVim's Preference pane, or directly set
|
||||
|MMAllowForceClickLookUp|.
|
||||
|
||||
You can map these keys like with any other key using the |:map| family of
|
||||
commands. For example, the following commands map left/right swipe to change
|
||||
|
||||
@@ -5417,6 +5417,7 @@ LogiPat-flags pi_logipat.txt /*LogiPat-flags*
|
||||
Lua if_lua.txt /*Lua*
|
||||
M motion.txt /*M*
|
||||
MDI starting.txt /*MDI*
|
||||
MMAllowForceClickLookUp gui_mac.txt /*MMAllowForceClickLookUp*
|
||||
MMAppearanceModeSelection gui_mac.txt /*MMAppearanceModeSelection*
|
||||
MMCellWidthMultiplier gui_mac.txt /*MMCellWidthMultiplier*
|
||||
MMCmdLineAlignBottom gui_mac.txt /*MMCmdLineAlignBottom*
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MMPreferenceController">
|
||||
<connections>
|
||||
<outlet property="advancedPreferences" destination="620" id="632"/>
|
||||
<outlet property="allowForceClickLookUpButton" destination="rlt-zw-mfW" id="xCv-HS-zyJ"/>
|
||||
<outlet property="appearancePreferences" destination="hr4-G4-3ZG" id="G54-DD-ACh"/>
|
||||
<outlet property="autoInstallUpdateButton" destination="UYM-W0-Kgl" id="cX5-tk-9WJ"/>
|
||||
<outlet property="generalPreferences" destination="115" id="143"/>
|
||||
<outlet property="inputPreferences" destination="Bnq-Nx-GJH" id="FES-rQ-Fpa"/>
|
||||
<outlet property="layoutPopUpButton" destination="427" id="596"/>
|
||||
<outlet property="sparkleUpdaterPane" destination="0hT-y8-Hge" id="e0L-sv-OCW"/>
|
||||
</connections>
|
||||
@@ -475,6 +477,40 @@
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="137.5" y="435"/>
|
||||
</customView>
|
||||
<customView id="Bnq-Nx-GJH" userLabel="Input">
|
||||
<rect key="frame" x="0.0" y="0.0" width="483" height="58"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<customView id="DAP-Yi-QU0" userLabel="Trackpad">
|
||||
<rect key="frame" x="20" y="20" width="433" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="f18-Wr-EgZ">
|
||||
<rect key="frame" x="-2" y="0.0" width="187" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Trackpad:" id="jkp-Ls-ZhN">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button id="rlt-zw-mfW" userLabel="Force click option">
|
||||
<rect key="frame" x="189" y="-1" width="213" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
|
||||
<string key="toolTip">Allow Force clicks to look up data when it is configured to do so (under "Trackpad" in System Settings). This will prevent <ForceClick> mappings in Vim from working. If you rely on <ForceClick> mappings, you may want to unset this option. This setting does not matter if you have configured to use three-finger taps to look up instead, as <ForceClick> will always work.</string>
|
||||
<buttonCell key="cell" type="check" title="Use Force click to look up data" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="A5o-Il-XdJ" userLabel="Force click option">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="58" name="value" keyPath="values.MMAllowForceClickLookUp" id="sef-c3-KyZ"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</customView>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="137.5" y="679"/>
|
||||
</customView>
|
||||
<customView id="620" userLabel="Advanced">
|
||||
<rect key="frame" x="0.0" y="0.0" width="483" height="264"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
|
||||
@@ -256,6 +256,7 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
|
||||
[NSNumber numberWithBool:YES], MMShareFindPboardKey,
|
||||
[NSNumber numberWithBool:NO], MMSmoothResizeKey,
|
||||
[NSNumber numberWithBool:NO], MMCmdLineAlignBottomKey,
|
||||
[NSNumber numberWithBool:YES], MMAllowForceClickLookUpKey,
|
||||
nil];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
|
||||
|
||||
@@ -13,8 +13,31 @@
|
||||
@class MMTextViewHelper;
|
||||
|
||||
|
||||
/// The main text view that manages drawing Vim's content using Core Text, and
|
||||
/// handles input. We are using this instead of NSTextView because of the
|
||||
/// custom needs in order to draw Vim's texts, as we don't have access to the
|
||||
/// full contents of Vim, and works more like a smart terminal to Vim.
|
||||
///
|
||||
/// Currently the rendering is done in software via Core Text, but a future
|
||||
/// extension will add support for Metal rendering which probably will require
|
||||
/// splitting this class up.
|
||||
///
|
||||
/// Since this class implements text rendering/input using a custom view, it
|
||||
/// implements NSTextInputClient, mostly for the following needs:
|
||||
/// 1. Text input. This is done via insertText / doCommandBySelector.
|
||||
/// 2. Input methods (e.g. for CJK). This is done via the marked text and the
|
||||
/// other APIs like selectedRange/firstRectForCharacterRange/etc.
|
||||
/// 3. Support native dictionary lookup (quickLookWithEvent:) when the user
|
||||
/// wants to. This mostly involves implementing the attributeSubstring /
|
||||
/// firstRectForCharacterRange / characterIndexForPoint APIs.
|
||||
/// There is an inherent difficulty to implementing NSTextInputClient
|
||||
/// 'correctly', because it assumes we have an entire text storage with
|
||||
/// indexable ranges. However, we don't have full access to Vim's internal
|
||||
/// storage, and we are represening the screen view instead in row-major
|
||||
/// indexing, but this becomes complicated when we want to implement marked
|
||||
/// texts. We the relevant parts for comments on how we hack around this.
|
||||
@interface MMCoreTextView : NSView <
|
||||
NSTextInput
|
||||
NSTextInputClient
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
|
||||
, NSFontChanging
|
||||
, NSMenuItemValidation
|
||||
@@ -122,8 +145,21 @@
|
||||
// NSTextView methods
|
||||
//
|
||||
- (void)keyDown:(NSEvent *)event;
|
||||
- (void)insertText:(id)string;
|
||||
|
||||
//
|
||||
// NSTextInputClient methods
|
||||
//
|
||||
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange;
|
||||
- (void)doCommandBySelector:(SEL)selector;
|
||||
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange;
|
||||
- (void)unmarkText;
|
||||
- (NSRange)selectedRange;
|
||||
- (NSRange)markedRange;
|
||||
- (BOOL)hasMarkedText;
|
||||
- (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange;
|
||||
- (nonnull NSArray<NSAttributedStringKey> *)validAttributesForMarkedText;
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange;
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point;
|
||||
|
||||
//
|
||||
// NSTextContainer methods
|
||||
|
||||
+399
-45
@@ -596,8 +596,9 @@ static void grid_free(Grid *grid) {
|
||||
[helper keyDown:event];
|
||||
}
|
||||
|
||||
- (void)insertText:(id)string
|
||||
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
|
||||
{
|
||||
// We are not currently replacementRange right now.
|
||||
[helper insertText:string];
|
||||
}
|
||||
|
||||
@@ -606,36 +607,6 @@ static void grid_free(Grid *grid) {
|
||||
[helper doCommandBySelector:selector];
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [helper hasMarkedText];
|
||||
}
|
||||
|
||||
- (NSRange)markedRange
|
||||
{
|
||||
return [helper markedRange];
|
||||
}
|
||||
|
||||
- (NSDictionary *)markedTextAttributes
|
||||
{
|
||||
return [helper markedTextAttributes];
|
||||
}
|
||||
|
||||
- (void)setMarkedTextAttributes:(NSDictionary *)attr
|
||||
{
|
||||
[helper setMarkedTextAttributes:attr];
|
||||
}
|
||||
|
||||
- (void)setMarkedText:(id)text selectedRange:(NSRange)range
|
||||
{
|
||||
[helper setMarkedText:text selectedRange:range];
|
||||
}
|
||||
|
||||
- (void)unmarkText
|
||||
{
|
||||
[helper unmarkText];
|
||||
}
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event
|
||||
{
|
||||
[helper scrollWheel:event];
|
||||
@@ -1283,41 +1254,424 @@ static void grid_free(Grid *grid) {
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (NSArray *)validAttributesForMarkedText
|
||||
#pragma mark Text Input Client
|
||||
#pragma region Text Input Client
|
||||
|
||||
//
|
||||
// Text input client implementation.
|
||||
//
|
||||
// Note that we are implementing this as a row-major indexed grid of the
|
||||
// current display. This is not the same as Vim's internal knowledge of the
|
||||
// buffers. We don't really have access to that easily because MacVim is purely
|
||||
// a GUI into Vim through a multi-process model. It's theoretically possible to
|
||||
// get access to it, but it increases latency and complexity, and we won't be
|
||||
// able to get access to the message output.
|
||||
//
|
||||
// Because of this quirk, proper marked text implementation is quite difficult.
|
||||
// The OS assumes a proper text strage backing, and that marked texts are a
|
||||
// contiguous region in that storage (see how markedRange API returns a single
|
||||
// NSRange). This is not possible for us if Vim has the marked texts wrapped
|
||||
// into multiple lines while we have split windows, or just that a long marked
|
||||
// text could be hidden. Because of that, we fake it by testing for
|
||||
// hasMarkedText: If we have marked texts, we always tell the OS we are
|
||||
// starting from 0, and the selectedRange/markedRange/etc all treat the text
|
||||
// storage as having the marked text starting from 0, and
|
||||
// firstRectForCharacterRange just handles that specially to make sure we still
|
||||
// draw the input method's candidate list properly. Otherwise, we just treat
|
||||
// the text storage as a row-major grid of the currently displayed text, which
|
||||
// works fine for dictionary lookups.
|
||||
//
|
||||
// Also, note that whenever the OS API uses a character index or range, it
|
||||
// always refers to the unicode length, so the calculation between row/col and
|
||||
// character index/range needs to go through each character and calculate its
|
||||
// length. We could optimize it to cache each row's total char length if we
|
||||
// want if this is an issue.
|
||||
//
|
||||
|
||||
/// Takes a point and convert it into a single index into the entire window's
|
||||
/// text. The text is converted into a row-major format, and the lines are
|
||||
/// concatenated together without injecting any spaces or newlines. Note that
|
||||
/// this doesn't take into account of Vim's own window splits and whatnot for
|
||||
/// now so a wrapped text in Vim would not be returned as contiguous.
|
||||
///
|
||||
/// The concatenation is done without injecting newlines for simplicity and to
|
||||
/// allow wrapped lines to come together but that could be changed if it's
|
||||
/// undesired.
|
||||
static NSUInteger utfCharIndexFromRowCol(const Grid* grid, int row, int col)
|
||||
{
|
||||
return nil;
|
||||
// Find the raw index for the character. Note that this is not good enough. With localized / wide texts,
|
||||
// some character will be single-width but have length > 1, and some character will be double-width. We
|
||||
// don't pre-calculate these information (since this is needed infrequently), and so we have to search
|
||||
// from first character onwards and accumulating the lengths.
|
||||
// See attributedSubstringForProposedRange which also does the same thing.
|
||||
const int rawIndex = row * grid->cols + col;
|
||||
const int gridSize = grid->cols * grid->rows;
|
||||
|
||||
NSUInteger utfIndex = 0;
|
||||
for (int i = 0; i < gridSize && i < rawIndex; i++) {
|
||||
NSString *str = grid->cells[i].string;
|
||||
utfIndex += str == nil ? 1 : str.length; // Note: nil string means empty space.
|
||||
|
||||
if (grid->cells[i].textFlags & DRAW_WIDE) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return utfIndex;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range
|
||||
/// Given grid position, and a UTF-8 character offset, return the new column on
|
||||
/// the same line. This doesn't support multi-line for now as there is no need
|
||||
/// to.
|
||||
///
|
||||
/// @param utfIndexOffset The character offset from the row/col provided. Can
|
||||
/// be positive or negative.
|
||||
///
|
||||
/// @return The column at the specified offset. Note that this clamps at
|
||||
/// [0,cols-1] since we are only looking for the same line.
|
||||
static int colFromUtfOffset(const Grid* grid, int row, int col, NSInteger utfIndexOffset)
|
||||
{
|
||||
return nil;
|
||||
if (row < 0 || col < 0 || row >= grid->rows || col >= grid->cols) {
|
||||
// Should not happen
|
||||
return 0;
|
||||
}
|
||||
if (utfIndexOffset == 0)
|
||||
return col;
|
||||
|
||||
const int advance = utfIndexOffset > 0 ? 1 : -1;
|
||||
NSUInteger accUtfIndexOffset = 0;
|
||||
|
||||
int c;
|
||||
for (c = col; c > 0 && c < grid->cols - 1 && accUtfIndexOffset < labs(utfIndexOffset); c += advance) {
|
||||
int rawIndex = row * grid->cols + c;
|
||||
|
||||
if (advance < 0) {
|
||||
// If going backwards, we have to use the last character's length
|
||||
// instead, including walking back 2 chars if it happens to be a
|
||||
// wide char.
|
||||
rawIndex -= 1;
|
||||
if (c - 2 >= 0 && grid->cells[rawIndex - 1].textFlags & DRAW_WIDE) {
|
||||
c += advance;
|
||||
rawIndex -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *str = grid->cells[rawIndex].string;
|
||||
accUtfIndexOffset += str == nil ? 1 : str.length; // Note: nil string means empty space.
|
||||
|
||||
if (advance > 0) {
|
||||
if (grid->cells[rawIndex].textFlags & DRAW_WIDE) {
|
||||
c += advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure nothing out of bounds happened due to some issue with wide-character skipping.
|
||||
if (c < 0)
|
||||
c = 0;
|
||||
if (c >= grid->cols)
|
||||
c = grid->cols - 1;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/// Given a range of UTF-8 character indices, find the row/col of the beginning
|
||||
/// of the range, and the end of the range *on the same line*. This doesn't
|
||||
/// support searching for the end past the first line because there's no need
|
||||
/// to right now. Sort of the reverse of utfCharIndexFromRowCol.
|
||||
///
|
||||
/// This assumes the text representation is a row-major representation of the
|
||||
/// whole grid, with no newline/spaces to separate the lines.
|
||||
///
|
||||
/// @param row Return the starting character's row.
|
||||
/// @param col Return the starting character's column.
|
||||
/// @param firstLineNumCols Return the number of columns to the end character's
|
||||
/// on the same line. If the end char is on the next line, then this
|
||||
/// will just find the last column of the line.
|
||||
/// @param firstLineUtf8Len Return the length of the characters on the first
|
||||
/// line, in UTF-8 length.
|
||||
static void rowColFromUtfRange(const Grid* grid, NSRange range,
|
||||
int *row, int *col,
|
||||
int *firstLineNumCols, int *firstLineUtf8Len)
|
||||
{
|
||||
int startUtfIndex = -1;
|
||||
int outRow = -1;
|
||||
int outCol = -1;
|
||||
int outFirstLineNumCols = -1;
|
||||
int outFirstLineLen = -1;
|
||||
|
||||
const int gridSize = grid->cols * grid->rows;
|
||||
NSUInteger utfIndex = 0;
|
||||
for (int i = 0; i < gridSize; i++) {
|
||||
if (utfIndex >= range.location) {
|
||||
// We are now past the start of the character.
|
||||
const int curRow = i / grid->cols;
|
||||
const int curCol = i % grid->cols;
|
||||
|
||||
if (outRow == -1) {
|
||||
// Record the beginning
|
||||
startUtfIndex = utfIndex;
|
||||
outRow = curRow;
|
||||
outCol = curCol;
|
||||
}
|
||||
|
||||
if (utfIndex >= range.location + range.length) {
|
||||
// Record the end if we found it.
|
||||
if (outFirstLineNumCols == -1) {
|
||||
outFirstLineLen = utfIndex - startUtfIndex;
|
||||
outFirstLineNumCols = curCol - outCol;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (curRow > outRow) {
|
||||
// We didn't find the end, but we are already at next line, so
|
||||
// just clamp it to the last column from the last line.
|
||||
outFirstLineLen = utfIndex - startUtfIndex;
|
||||
outFirstLineNumCols = grid->cols - outCol;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NSString *str = grid->cells[i].string;
|
||||
utfIndex += str == nil ? 1 : str.length; // Note: nil string means empty space.
|
||||
|
||||
if (grid->cells[i].textFlags & DRAW_WIDE) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (outRow == -1)
|
||||
{
|
||||
*row = 0;
|
||||
*col = 1;
|
||||
*firstLineNumCols = 0;
|
||||
*firstLineUtf8Len = 0;
|
||||
return;
|
||||
}
|
||||
if (outFirstLineNumCols == -1)
|
||||
{
|
||||
outFirstLineLen = utfIndex - startUtfIndex;
|
||||
outFirstLineNumCols = grid->cols;
|
||||
}
|
||||
*row = outRow;
|
||||
*col = outCol;
|
||||
*firstLineNumCols = outFirstLineNumCols;
|
||||
*firstLineUtf8Len = outFirstLineLen;
|
||||
}
|
||||
|
||||
- (nonnull NSArray<NSAttributedStringKey> *)validAttributesForMarkedText
|
||||
{
|
||||
// Not implementing this for now. Properly implementing this would allow things like bolded underline
|
||||
// for certain texts in the marked range, etc, but we would need SetMarkedTextMsgID to support it.
|
||||
return @[];
|
||||
}
|
||||
|
||||
/// Returns an attributed string containing the proposed range. This method is
|
||||
/// usually called for two reasons:
|
||||
/// 1. Input methods. It's unclear why the OS calls this during marked text
|
||||
/// operation and returning nil doesn't seem to have any negative effect.
|
||||
/// However, for operations like Hangul->Hanja (by pressing Option-Return),
|
||||
/// it does rely on this after inserting the original Hangul text.
|
||||
/// 2. Dictionary lookup. This is used for retrieving the formatted text that
|
||||
/// the OS uses to look up and to show within the yellow box.
|
||||
- (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange;
|
||||
{
|
||||
// Because of Unicode / wide characters, we have to unfortunately loop through the entire text to
|
||||
// find the range. We could add better accelerated data structure here if for some reason this is
|
||||
// slow (it should only be called when inputting special / localized characters or when doing
|
||||
// quickLook lookup (e.g. Cmd-Ctrl-D). This step is important though or emojis and say Chinese
|
||||
// characters would not behave properly. See characterIndexForPoint which also does the same thing.
|
||||
|
||||
if (range.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([helper hasMarkedText]) {
|
||||
// Since marked text changes the meaning of the text storage ranges (see above overall design),
|
||||
// just don't return anything for now. We could simply return the marked text if we want to and
|
||||
// have a need to do so.
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableString *retStr = nil;
|
||||
NSUInteger utfIndex = 0;
|
||||
|
||||
const int gridSize = grid.cols * grid.rows;
|
||||
for (int i = 0; i < gridSize; i++) {
|
||||
NSString *str = grid.cells[i].string;
|
||||
if (str == nil) {
|
||||
str = @" ";
|
||||
}
|
||||
|
||||
if (utfIndex >= range.location) {
|
||||
if (retStr == nil) {
|
||||
// Lazily initialize the return string in case the passed in range is just completely
|
||||
// out of bounds.
|
||||
retStr = [NSMutableString stringWithCapacity:range.length];;
|
||||
}
|
||||
[retStr appendString:str];
|
||||
}
|
||||
if (retStr.length >= range.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Increment counters
|
||||
utfIndex += str.length;
|
||||
if (grid.cells[i].textFlags & DRAW_WIDE) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (retStr == nil) {
|
||||
return nil;
|
||||
}
|
||||
if (actualRange != NULL) {
|
||||
actualRange->length = retStr.length;
|
||||
}
|
||||
// Return an attributed string with the correct font so it will long right.
|
||||
// Note that this won't get us a perfect replica of the displayed texts,
|
||||
// but good enough. Some reasons why it's not perfect:
|
||||
// - Asian characters don't get displayed in double-width under OS
|
||||
// rendering and will be narrower.
|
||||
// - We aren't passing through bold/italics/underline/strike-through/etc
|
||||
// for now. This is probably ok. If we want to tackle this maybe just
|
||||
// bold/underline is enough. Even NSTextView doesn't pass the
|
||||
// underline/etc styles over, presumably because they make reading it
|
||||
// hard.
|
||||
// - Font substitutions aren't handled the same way.
|
||||
return [[[NSAttributedString alloc] initWithString:retStr
|
||||
attributes:@{NSFontAttributeName: font}
|
||||
] autorelease];
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [helper hasMarkedText];
|
||||
}
|
||||
|
||||
- (NSRange)markedRange
|
||||
{
|
||||
// This will return the range marked from 0 to size of marked text. See the
|
||||
// overall text input client implementation above for more description of
|
||||
// the design choice of handling marked text in this API.
|
||||
return [helper markedRange];
|
||||
}
|
||||
|
||||
- (NSDictionary *)markedTextAttributes
|
||||
{
|
||||
return [helper markedTextAttributes];
|
||||
}
|
||||
|
||||
- (void)setMarkedTextAttributes:(NSDictionary *)attr
|
||||
{
|
||||
[helper setMarkedTextAttributes:attr];
|
||||
}
|
||||
|
||||
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange;
|
||||
{
|
||||
// We are not using replacementRange right now
|
||||
[helper setMarkedText:string selectedRange:selectedRange];
|
||||
}
|
||||
|
||||
- (void)unmarkText
|
||||
{
|
||||
[helper unmarkText];
|
||||
}
|
||||
|
||||
/// Returns a character index to the overall text storage.
|
||||
///
|
||||
/// This is used mostly for quickLookWithEvent: calls for the OS to be able to
|
||||
/// understand the textual content of this text input client.
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)point
|
||||
{
|
||||
return NSNotFound;
|
||||
}
|
||||
// Not using convertPointFromScreen because it's 10.12+ only.
|
||||
NSRect screenRect = {point, NSZeroSize};
|
||||
NSPoint windowPt = [[self window] convertRectFromScreen:screenRect].origin;
|
||||
NSPoint viewPt = [self convertPoint:windowPt fromView:nil];
|
||||
int row, col;
|
||||
if (![self convertPoint:viewPt toRow:&row column:&col]) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (NSInteger)conversationIdentifier
|
||||
{
|
||||
return (NSInteger)self;
|
||||
return utfCharIndexFromRowCol(&grid, row, col);
|
||||
}
|
||||
|
||||
- (NSRange)selectedRange
|
||||
{
|
||||
return [helper imRange];
|
||||
if ([helper hasMarkedText]) {
|
||||
// This returns the current cursor position relative to the marked
|
||||
// range, starting from 0. See above overall comments on text input
|
||||
// client implementation for marked text API decision.
|
||||
return [helper imRange];
|
||||
}
|
||||
|
||||
// Find the character index.
|
||||
int row = [helper preEditRow];
|
||||
int col = [helper preEditColumn];
|
||||
NSUInteger charIndex = utfCharIndexFromRowCol(&grid, row, col);
|
||||
|
||||
// We don't support selected texts for now, so always return length = 0;
|
||||
NSRange result = {charIndex, 0};
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range
|
||||
/// Return the first line's rectangle for a range of characters. This is
|
||||
/// usually called either during marked text operation to decide where to show
|
||||
/// a candidate list, or when doing dictionary lookup and the UI wants to draw
|
||||
/// a box right on top of this text seamlessly.
|
||||
///
|
||||
/// @param range The range to show rect for. Note that during marked text
|
||||
/// operation, this could be different from imRange. For example, when using
|
||||
/// Japanese input to input a long line of text, the user could use
|
||||
/// left/right arrow keys to jump to different section of the
|
||||
/// in-progress phrase and pick a new candidate. When doing that, this
|
||||
/// will get called with different range's in order to show the
|
||||
/// candidate list box right below the current section under
|
||||
/// consideration.
|
||||
/// @param actualRange The actual range this rect represents. Only used for
|
||||
/// non-marked text situations for now.
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(nullable NSRangePointer)actualRange
|
||||
{
|
||||
return [helper firstRectForCharacterRange:range];
|
||||
if ([helper hasMarkedText]) {
|
||||
// Marked texts have special handling (see above overall comments for
|
||||
// marked text API design).
|
||||
|
||||
// Because we just expose the range as 0 to marked length, the range
|
||||
// here doesn't represent the final screen position. Instead, we use
|
||||
// the current cursor position as basis. We know that during marked
|
||||
// text operations it has to be inside the marked range as specified by
|
||||
// setMarkedText's range.
|
||||
const int cursorRow = [helper preEditRow];
|
||||
const int cursorCol = [helper preEditColumn];
|
||||
|
||||
// Now, we retrieve the IM range that setMarkedText gave us, and
|
||||
// compare with what the OS wants now (range). Find the rectangle
|
||||
// surrounding that.
|
||||
const NSRange imRange = [helper imRange];
|
||||
const NSInteger startIndexOffset = range.location - imRange.location;
|
||||
const NSInteger endIndexOffset = range.location + range.length - imRange.location;
|
||||
|
||||
const int rectBeginCol = colFromUtfOffset(&grid, cursorRow, cursorCol, startIndexOffset);
|
||||
const int rectEndCol = colFromUtfOffset(&grid, cursorRow, cursorCol, endIndexOffset);
|
||||
|
||||
return [helper firstRectForCharacterRange:cursorRow column:rectBeginCol length:(rectEndCol - rectBeginCol)];
|
||||
} else {
|
||||
int row = 0, col = 0, firstLineNumCols = 0, firstLineUtf8Len = 0;
|
||||
rowColFromUtfRange(&grid, range, &row, &col, &firstLineNumCols, &firstLineUtf8Len);
|
||||
if (actualRange != NULL) {
|
||||
actualRange->location = range.location;
|
||||
actualRange->length = firstLineUtf8Len;
|
||||
}
|
||||
return [helper firstRectForCharacterRange:row column:col length:firstLineNumCols];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion // Text Input Client
|
||||
|
||||
@end // MMCoreTextView
|
||||
|
||||
|
||||
|
||||
|
||||
@implementation MMCoreTextView (Private)
|
||||
|
||||
- (MMWindowController *)windowController
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
@interface MMPreferenceController : DBPrefsWindowController {
|
||||
IBOutlet NSView *generalPreferences;
|
||||
IBOutlet NSView *appearancePreferences;
|
||||
IBOutlet NSView *inputPreferences;
|
||||
IBOutlet NSView *advancedPreferences;
|
||||
|
||||
// General pane
|
||||
IBOutlet NSPopUpButton *layoutPopUpButton;
|
||||
IBOutlet NSButton *autoInstallUpdateButton;
|
||||
IBOutlet NSView *sparkleUpdaterPane;
|
||||
|
||||
// Input pane
|
||||
IBOutlet NSButton *allowForceClickLookUpButton;
|
||||
}
|
||||
|
||||
// General pane
|
||||
|
||||
@@ -43,6 +43,17 @@
|
||||
{
|
||||
[super setCrossFade:NO];
|
||||
[super showWindow:sender];
|
||||
|
||||
// Refresh enabled states for settings that may or may not make sense
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
if (allowForceClickLookUpButton != nil) {
|
||||
// Only enable force click lookup setting if only the user has configured so to begin with.
|
||||
// Otherwise it doesn't make sense at all.
|
||||
// Note: This cannot be done in simple bindings, because NSUserDefaults don't really support
|
||||
// global domain bindings from what I can tell, we have to manually read it.
|
||||
const BOOL useForceClickLookup = [ud boolForKey:@"com.apple.trackpad.forceClick"];
|
||||
[allowForceClickLookUpButton setEnabled:useForceClickLookup];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupToolbar
|
||||
@@ -58,6 +69,10 @@
|
||||
label:@"Appearance"
|
||||
image:[NSImage imageWithSystemSymbolName:@"paintbrush" accessibilityDescription:nil]];
|
||||
|
||||
[self addView:inputPreferences
|
||||
label:@"Input"
|
||||
image:[NSImage imageWithSystemSymbolName:@"keyboard" accessibilityDescription:nil]];
|
||||
|
||||
[self addView:advancedPreferences
|
||||
label:@"Advanced"
|
||||
image:[NSImage imageWithSystemSymbolName:@"gearshape.2" accessibilityDescription:nil]];
|
||||
@@ -73,6 +88,10 @@
|
||||
label:@"Appearance"
|
||||
image:[NSImage imageNamed:NSImageNameColorPanel]];
|
||||
|
||||
[self addView:inputPreferences
|
||||
label:@"Input"
|
||||
image:[NSImage imageNamed:NSImageNamePreferencesGeneral]]; // not a good choice but works for now
|
||||
|
||||
[self addView:advancedPreferences
|
||||
label:@"Advanced"
|
||||
image:[NSImage imageNamed:NSImageNameAdvanced]];
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
NSRange markedRange;
|
||||
NSDictionary *markedTextAttributes;
|
||||
NSMutableAttributedString *markedText;
|
||||
int preEditRow;
|
||||
int preEditColumn;
|
||||
int preEditRow; ///< The cursor's row. Note that this gets set no matter what. Doesn't matter if we are in pre-edit or not.
|
||||
int preEditColumn; ///< The cursor's column.
|
||||
BOOL imControl;
|
||||
BOOL imState;
|
||||
TISInputSourceRef lastImSource;
|
||||
@@ -90,6 +90,7 @@
|
||||
- (NSRange)imRange;
|
||||
- (void)setMarkedRange:(NSRange)range;
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range;
|
||||
- (NSRect)firstRectForCharacterRange:(int)row column:(int)col length:(int)length;
|
||||
- (void)setImControl:(BOOL)enable;
|
||||
- (void)activateIm:(BOOL)enable;
|
||||
- (BOOL)useInlineIm;
|
||||
|
||||
@@ -503,8 +503,29 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
|
||||
if (event.stage >= 2) {
|
||||
if (!inForceClick) {
|
||||
inForceClick = YES;
|
||||
|
||||
[self sendGestureEvent:MMGestureForceClick flags:[event modifierFlags]];
|
||||
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
// See if the OS is configured to use Force click for data lookups
|
||||
// (the other option usually being three-finger tap).
|
||||
const BOOL useForceClickLookup = [ud boolForKey:@"com.apple.trackpad.forceClick"];
|
||||
|
||||
// See if the user has overriden to disallow Force click lookups.
|
||||
// The usual reason for disallowing it is to support binding
|
||||
// <ForceClick> mappings in Vim.
|
||||
const BOOL userAllowsForceClickLookup = [ud boolForKey:MMAllowForceClickLookUpKey];
|
||||
|
||||
if (useForceClickLookup && userAllowsForceClickLookup) {
|
||||
// For some odd reason, we don't get quickLookWithEvent: even when
|
||||
// the user has configured to use force click instead of 3-finger
|
||||
// tap. We need to manually invoke it (this is how NSTextView does
|
||||
// it as well). References for other software that do this:
|
||||
// https://gitlab.com/gnachman/iterm2/-/blob/master/sources/PointerController.m
|
||||
// https://searchfox.org/mozilla-central/source/widget/cocoa/nsChildView.mm
|
||||
[textView quickLookWithEvent:event];
|
||||
} else {
|
||||
[self sendGestureEvent:MMGestureForceClick flags:[event modifierFlags]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inForceClick = NO;
|
||||
@@ -751,8 +772,23 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
|
||||
markedRange = range;
|
||||
}
|
||||
|
||||
/// Don't use this. See comments.
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)range
|
||||
{
|
||||
//
|
||||
// Note: This is really quite a buggy method and relies on improper
|
||||
// assumptions. It's kept alive for now because MMTextView (which is also
|
||||
// deprecated and shouldn't be used for real users) uses this.
|
||||
// Known bugs:
|
||||
// - Assumes that preEditRow/Column is the beginning of the marked range,
|
||||
// but the way it actually works is that it's the current cursor *within*
|
||||
// the marked range.
|
||||
// - Uses fontWide to decide to jump 1 or 2 columns per character. First,
|
||||
// this is wrong, as wide texts work just fine even without guifontwide
|
||||
// set. Second, some characters may have length > 1. See MMCoreTextView
|
||||
// which does proper length calculation.
|
||||
//
|
||||
|
||||
// This method is called when the input manager wants to pop up an
|
||||
// auxiliary window. The position where this should be is controlled by
|
||||
// Vim by sending SetPreEditPositionMsgID so compute a position based on
|
||||
@@ -761,13 +797,13 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
|
||||
int row = preEditRow;
|
||||
|
||||
NSFont *theFont = [[textView markedTextAttributes]
|
||||
valueForKey:NSFontAttributeName];
|
||||
valueForKey:NSFontAttributeName];
|
||||
if (theFont == [textView fontWide]) {
|
||||
col += imRange.location * 2;
|
||||
if (col >= [textView maxColumns] - 1) {
|
||||
row += (col / [textView maxColumns]);
|
||||
col = col % 2 ? col % [textView maxColumns] + 1 :
|
||||
col % [textView maxColumns];
|
||||
col % [textView maxColumns];
|
||||
}
|
||||
} else {
|
||||
col += imRange.location;
|
||||
@@ -777,10 +813,15 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
|
||||
}
|
||||
}
|
||||
|
||||
return [self firstRectForCharacterRange:row column:col length:range.length];
|
||||
}
|
||||
|
||||
- (NSRect)firstRectForCharacterRange:(int)row column:(int)col length:(int)numColumns
|
||||
{
|
||||
NSRect rect = [textView rectForRow:row
|
||||
column:col
|
||||
numRows:1
|
||||
numColumns:range.length];
|
||||
numColumns:numColumns];
|
||||
|
||||
// NOTE: If the text view is flipped then 'rect' has its origin in the top
|
||||
// left corner of the rect, but the methods below expect it to be in the
|
||||
|
||||
@@ -61,6 +61,7 @@ extern NSString *MMNonNativeFullScreenShowMenuKey;
|
||||
extern NSString *MMNonNativeFullScreenSafeAreaBehaviorKey;
|
||||
extern NSString *MMSmoothResizeKey;
|
||||
extern NSString *MMCmdLineAlignBottomKey;
|
||||
extern NSString *MMAllowForceClickLookUpKey;
|
||||
|
||||
|
||||
// Enum for MMUntitledWindowKey
|
||||
|
||||
@@ -57,6 +57,7 @@ NSString *MMNonNativeFullScreenShowMenuKey = @"MMNonNativeFullScreenShowMenu";
|
||||
NSString *MMNonNativeFullScreenSafeAreaBehaviorKey = @"MMNonNativeFullScreenSafeAreaBehavior";
|
||||
NSString *MMSmoothResizeKey = @"MMSmoothResize";
|
||||
NSString *MMCmdLineAlignBottomKey = @"MMCmdLineAlignBottom";
|
||||
NSString *MMAllowForceClickLookUpKey = @"MMAllowForceClickLookUp";
|
||||
|
||||
|
||||
@implementation NSIndexSet (MMExtras)
|
||||
|
||||
Reference in New Issue
Block a user