MacVim のインライン変換で、変換対象の文節の範囲とそれ以外との表示に差がない問題を修正する

  • 環境:MacVim release 174 + ATOK (2022-12-28の最新版)
  • 問題点:インラインIM変換対象の文節のアンダーラインが、変換対象でない部分との表示の差がなく、文節長を調節する際などに不便。
    • たとえば下の画像では「吾輩は」までが変換対象の文節になっているのだが、「猫である」と表示に差がない。

MacVim release 174 での変換中の文字列

MacVimとMacVim-KaoriYaの差分 を参考に、問題に関与していると思われる部分を取り込んだパッチを作成した(ページの下部に diff を掲載する)。パッチを当てると、下の画像のように変換対象文字列が他の部分と区別できるようになった。

パッチを当てた後の変換中の文字列

パッチ(MacVim r179用):

diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m
index 523cd5d37..faf9e6b09 100644
--- a/src/MacVim/MMBackend.m
+++ b/src/MacVim/MMBackend.m
@@ -3496,21 +3496,22 @@ static char_u *extractSelectedText(void)
 - (void)handleMarkedText:(NSData *)data
 {
     const void *bytes = [data bytes];
+    unsigned textlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
     int32_t pos = *((int32_t*)bytes);  bytes += sizeof(int32_t);
     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
     char *chars = (char *)bytes;
 
-    ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
+    ASLogDebug(@"textlen=%d pos=%d len=%d chars=%s", textlen, pos, len, chars);
 
     if (pos < 0) {
         im_preedit_abandon_macvim();
-    } else if (len == 0) {
+    } else if (textlen == 0) {
    im_preedit_end_macvim();
     } else {
         if (!preedit_get_status())
             im_preedit_start_macvim();
 
-  im_preedit_changed_macvim(chars, pos);
+   im_preedit_changed_macvim(chars, pos, pos + len);
     }
 }
 
diff --git a/src/MacVim/MMTextViewHelper.m b/src/MacVim/MMTextViewHelper.m
index b2834f7bd..348f047f5 100644
--- a/src/MacVim/MMTextViewHelper.m
+++ b/src/MacVim/MMTextViewHelper.m
@@ -42,7 +42,7 @@ static float MMDragAreaSize = 73.0f;
 - (void)setCursor;
 - (NSRect)trackingRect;
 - (BOOL)inputManagerHandleMouseEvent:(NSEvent *)event;
-- (void)sendMarkedText:(NSString *)text position:(int32_t)pos;
+- (void)sendMarkedText:(NSString *)text position:(int32_t)pos length:(unsigned)len;
 - (void)abandonMarkedText;
 - (void)sendGestureEvent:(int)gesture flags:(unsigned)flags;
 @end
@@ -224,7 +224,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
 - (void)insertText:(id)string
 {
     if ([self hasMarkedText]) {
-        [self sendMarkedText:nil position:0];
+        [self sendMarkedText:nil position:0 length:0];
 
         // NOTE: If this call is left out then the marked text isn't properly
         // erased when Return is used to accept the text.
@@ -364,7 +364,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     if ([self hasMarkedText]) {
         // We must clear the marked text since the cursor may move if the
         // marked text moves outside the view as a result of scrolling.
-        [self sendMarkedText:nil position:0];
+        [self sendMarkedText:nil position:0 length:0];
         [self unmarkText];
         [[NSTextInputContext currentInputContext] discardMarkedText];
     }
@@ -709,7 +709,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
             imRange = range;
         }
 
-        [self sendMarkedText:text position:(int32_t)range.location];
+        [self sendMarkedText:text position:(int32_t)range.location length:(unsigned)range.length];
         return;
     }
 
@@ -1199,19 +1199,20 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     return NO;
 }
 
-- (void)sendMarkedText:(NSString *)text position:(int32_t)pos
+- (void)sendMarkedText:(NSString *)text position:(int32_t)pos length:(unsigned)len
 {
     if (![self useInlineIm])
         return;
 
     NSMutableData *data = [NSMutableData data];
-    unsigned len = text == nil ? 0
+    unsigned textlen = text == nil ? 0
                     : (unsigned)[text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
 
+    [data appendBytes:&textlen length:sizeof(unsigned)];
     [data appendBytes:&pos length:sizeof(int32_t)];
     [data appendBytes:&len length:sizeof(unsigned)];
-    if (len > 0) {
-        [data appendBytes:[text UTF8String] length:len];
+    if (textlen > 0) {
+        [data appendBytes:[text UTF8String] length:textlen];
         [data appendBytes:"\x00" length:1];
     }
 
@@ -1225,7 +1226,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     // Send an empty marked text message with position set to -1 to indicate
     // that the marked text should be abandoned.  (If pos is set to 0 Vim will
     // send backspace sequences to delete the old marked text.)
-    [self sendMarkedText:nil position:-1];
+    [self sendMarkedText:nil position:-1 length:0];
     [[NSTextInputContext currentInputContext] discardMarkedText];
 }
 
diff --git a/src/gui_xim.c b/src/gui_xim.c
index 3a7abaef9..502e87d04 100644
--- a/src/gui_xim.c
+++ b/src/gui_xim.c
@@ -202,6 +202,7 @@ init_preedit_start_col(void)
 
 static int im_is_active           = FALSE; // IM is enabled for current mode
 static int preedit_is_active   = FALSE;
+static int im_preedit_start    = 0;    /* start offset in characters        */
 static int im_preedit_cursor   = 0;    // cursor offset in characters
 static int im_preedit_trailing = 0;    // number of characters after cursor
 
@@ -733,7 +734,7 @@ im_preedit_abandon_macvim(void)
 im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED)
 # else
     void
-im_preedit_changed_macvim(char *preedit_string, int cursor_index)
+im_preedit_changed_macvim(char *preedit_string, int start_index, int cursor_index)
 # endif
 {
 # ifndef FEAT_GUI_MACVIM
@@ -754,6 +755,8 @@ im_preedit_changed_macvim(char *preedit_string, int cursor_index)
    gtk_im_context_get_preedit_string(context,
                      &preedit_string, NULL,
                      NULL);
+# else
+    im_preedit_start = start_index;
 # endif
 
 #ifdef XIM_DEBUG
@@ -942,7 +945,10 @@ im_get_feedback_attr(int col UNUSED)
 
     return char_attr;
 # else
-    return HL_UNDERLINE;
+    if (col >= im_preedit_start && col < im_preedit_cursor)
+   return HL_UNDERCURL;
+    else
+   return HL_UNDERLINE;
 # endif
 }
 
diff --git a/src/proto/gui_xim.pro b/src/proto/gui_xim.pro
index 0768db7a2..ed8de0699 100644
--- a/src/proto/gui_xim.pro
+++ b/src/proto/gui_xim.pro
@@ -25,5 +25,5 @@ int call_imstatusfunc(void);
 void im_preedit_start_macvim(void);
 void im_preedit_end_macvim(void);
 void im_preedit_abandon_macvim(void);
-void im_preedit_changed_macvim(char *preedit_string, int cursor_index);
+void im_preedit_changed_macvim(char *preedit_string, int start_index, int cursor_index);
 /* vim: set ft=c : */

パッチ(旧):

diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m
index 3c3dcaddd..d9d35d8a2 100644
--- a/src/MacVim/MMBackend.m
+++ b/src/MacVim/MMBackend.m
@@ -59,7 +59,7 @@ static id evalExprCocoa(NSString * expr, NSString ** errstr);
 void im_preedit_start_macvim();
 void im_preedit_end_macvim();
 void im_preedit_abandon_macvim();
-void im_preedit_changed_macvim(char *preedit_string, int cursor_index);
+void im_preedit_changed_macvim(char *preedit_string, int start_index, int cursor_index);
 
 enum {
     MMBlinkStateNone = 0,
@@ -3290,21 +3290,22 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
 - (void)handleMarkedText:(NSData *)data
 {
     const void *bytes = [data bytes];
+    unsigned textlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
     int32_t pos = *((int32_t*)bytes);  bytes += sizeof(int32_t);
     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
     char *chars = (char *)bytes;
 
-    ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
+    ASLogDebug(@"textlen=%d pos=%d len=%d chars=%s", textlen, pos, len, chars);
 
     if (pos < 0) {
         im_preedit_abandon_macvim();
-    } else if (len == 0) {
+    } else if (textlen == 0) {
    im_preedit_end_macvim();
     } else {
         if (!preedit_get_status())
             im_preedit_start_macvim();
 
-  im_preedit_changed_macvim(chars, pos);
+   im_preedit_changed_macvim(chars, pos, pos + len);
     }
 }
 
diff --git a/src/MacVim/MMTextViewHelper.m b/src/MacVim/MMTextViewHelper.m
index 43b718522..63ab3f594 100644
--- a/src/MacVim/MMTextViewHelper.m
+++ b/src/MacVim/MMTextViewHelper.m
@@ -42,7 +42,7 @@ static float MMDragAreaSize = 73.0f;
 - (void)setCursor;
 - (NSRect)trackingRect;
 - (BOOL)inputManagerHandleMouseEvent:(NSEvent *)event;
-- (void)sendMarkedText:(NSString *)text position:(int32_t)pos;
+- (void)sendMarkedText:(NSString *)text position:(int32_t)pos length:(unsigned)len;
 - (void)abandonMarkedText;
 - (void)sendGestureEvent:(int)gesture flags:(int)flags;
 @end
@@ -224,7 +224,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
 - (void)insertText:(id)string
 {
     if ([self hasMarkedText]) {
-        [self sendMarkedText:nil position:0];
+        [self sendMarkedText:nil position:0 length:0];
 
         // NOTE: If this call is left out then the marked text isn't properly
         // erased when Return is used to accept the text.
@@ -335,7 +335,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     if ([self hasMarkedText]) {
         // We must clear the marked text since the cursor may move if the
         // marked text moves outside the view as a result of scrolling.
-        [self sendMarkedText:nil position:0];
+        [self sendMarkedText:nil position:0 length:0];
         [self unmarkText];
         [[NSTextInputContext currentInputContext] discardMarkedText];
     }
@@ -659,7 +659,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
             imRange = range;
         }
 
-        [self sendMarkedText:text position:range.location];
+        [self sendMarkedText:text position:range.location length:range.length];
         return;
     }
 
@@ -1129,19 +1129,20 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     return NO;
 }
 
-- (void)sendMarkedText:(NSString *)text position:(int32_t)pos
+- (void)sendMarkedText:(NSString *)text position:(int32_t)pos length:(unsigned)len
 {
     if (![self useInlineIm])
         return;
 
     NSMutableData *data = [NSMutableData data];
-    unsigned len = text == nil ? 0
+    unsigned textlen = text == nil ? 0
                     : [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
 
+    [data appendBytes:&textlen length:sizeof(unsigned)];
     [data appendBytes:&pos length:sizeof(int32_t)];
     [data appendBytes:&len length:sizeof(unsigned)];
-    if (len > 0) {
-        [data appendBytes:[text UTF8String] length:len];
+    if (textlen > 0) {
+        [data appendBytes:[text UTF8String] length:textlen];
         [data appendBytes:"\x00" length:1];
     }
 
@@ -1155,7 +1156,7 @@ KeyboardInputSourcesEqual(TISInputSourceRef a, TISInputSourceRef b)
     // Send an empty marked text message with position set to -1 to indicate
     // that the marked text should be abandoned.  (If pos is set to 0 Vim will
     // send backspace sequences to delete the old marked text.)
-    [self sendMarkedText:nil position:-1];
+    [self sendMarkedText:nil position:-1 length:0];
     [[NSTextInputContext currentInputContext] discardMarkedText];
 }
 
diff --git a/src/gui_xim.c b/src/gui_xim.c
index ce2e79c95..fdcf29aaa 100644
--- a/src/gui_xim.c
+++ b/src/gui_xim.c
@@ -184,6 +184,7 @@ init_preedit_start_col(void)
 
 static int im_is_active           = FALSE; // IM is enabled for current mode
 static int preedit_is_active   = FALSE;
+static int im_preedit_start    = 0;    /* start offset in characters        */
 static int im_preedit_cursor   = 0;    // cursor offset in characters
 static int im_preedit_trailing = 0;    // number of characters after cursor
 
@@ -715,7 +716,7 @@ im_preedit_abandon_macvim()
 im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED)
 # else
     void
-im_preedit_changed_macvim(char *preedit_string, int cursor_index)
+im_preedit_changed_macvim(char *preedit_string, int start_index, int cursor_index)
 # endif
 {
 # ifndef FEAT_GUI_MACVIM
@@ -736,6 +737,8 @@ im_preedit_changed_macvim(char *preedit_string, int cursor_index)
    gtk_im_context_get_preedit_string(context,
                      &preedit_string, NULL,
                      NULL);
+# else
+    im_preedit_start = start_index;
 # endif
 
 #ifdef XIM_DEBUG
@@ -924,7 +927,10 @@ im_get_feedback_attr(int col UNUSED)
 
     return char_attr;
 # else
-    return HL_UNDERLINE;
+    if (col >= im_preedit_start && col < im_preedit_cursor)
+   return HL_UNDERCURL;
+    else
+   return HL_UNDERLINE;
 # endif
 }