Skip to content

Enable IME text reconversion in editor view#3214

Merged
sdottaka merged 7 commits intomasterfrom
feature/ime-reconversion
Feb 22, 2026
Merged

Enable IME text reconversion in editor view#3214
sdottaka merged 7 commits intomasterfrom
feature/ime-reconversion

Conversation

@sdottaka
Copy link
Member

This PR adds IME reconversion support to CrystalEdit via WM_IME_REQUEST.

It implements handling for IMR_RECONVERTSTRING, IMR_CONFIRMRECONVERTSTRING,
and IMR_DOCUMENTFEED, allowing IMEs to perform text reconversion using the
current line as context.

Common RECONVERTSTRING construction logic is refactored into a helper
function to reduce duplication and improve readability.
Reconversion is limited to single-line ranges by design.

This enables standard IME reconversion behavior without impacting existing
input handling.

@sdottaka sdottaka requested a review from Copilot February 22, 2026 12:27
@sdottaka sdottaka added this to the v2.16.55 milestone Feb 22, 2026
@sdottaka sdottaka marked this pull request as ready for review February 22, 2026 12:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds IME reconversion support to CrystalEdit’s CCrystalTextView by handling WM_IME_REQUEST and constructing RECONVERTSTRING data from the current line / single-line selection context.

Changes:

  • Add WM_IME_REQUEST message handling for IMR_RECONVERTSTRING, IMR_CONFIRMRECONVERTSTRING, and IMR_DOCUMENTFEED.
  • Introduce a BuildReconvertString helper to centralize RECONVERTSTRING construction.
  • Constrain reconversion to single-line selections.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
Externals/crystaledit/editlib/ccrystaltextview.h Adds forward declaration and declares new IME reconversion handler/helper methods.
Externals/crystaledit/editlib/ccrystaltextview.cpp Hooks WM_IME_REQUEST and implements reconversion/document-feed handling via RECONVERTSTRING.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

sdottaka and others added 6 commits February 22, 2026 21:34
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@sdottaka sdottaka merged commit 558f5b3 into master Feb 22, 2026
2 of 3 checks passed
@sdottaka sdottaka deleted the feature/ime-reconversion branch February 22, 2026 12:47
Comment on lines +6852 to +6922
switch (wParam)
{
case IMR_RECONVERTSTRING:
{
CEPoint ptCursor = GetCursorPos();
int nLineIndex = ptCursor.y;
DWORD dwOffset = static_cast<DWORD>(ptCursor.x);
DWORD dwLen = 0;

if (IsSelection())
{
CEPoint s, e;
GetSelection(s, e);
if (s.y != e.y)
return 0;
nLineIndex = s.y;
dwOffset = static_cast<DWORD>(s.x);
dwLen = static_cast<DWORD>(e.x - s.x);
}

return BuildReconvertString(
reinterpret_cast<RECONVERTSTRING*>(lParam), nLineIndex, dwOffset, dwLen);
}

case IMR_CONFIRMRECONVERTSTRING:
{
RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
if (pReconv == nullptr)
return FALSE;

DWORD dwCompCharOffset = pReconv->dwCompStrOffset / sizeof(tchar_t);
DWORD dwCompCharLen = pReconv->dwCompStrLen;

CEPoint ptCursor = GetCursorPos();
int nLineIndex = ptCursor.y;

if (IsSelection())
{
CEPoint ptSelStart, ptSelEnd;
GetSelection(ptSelStart, ptSelEnd);
if (ptSelStart.y != ptSelEnd.y)
return FALSE; // Multi-line selection not supported
nLineIndex = ptSelStart.y;
}

// IME adjusted the range but resulted in zero length - reject this
int nLineLen = GetLineLength(nLineIndex);
if (dwCompCharLen == 0)
return FALSE;
if (dwCompCharOffset >= static_cast<DWORD>(nLineLen))
return FALSE;
if (dwCompCharOffset + dwCompCharLen > static_cast<DWORD>(nLineLen))
return FALSE;

CEPoint ptNewStart(static_cast<int>(dwCompCharOffset), nLineIndex);
CEPoint ptNewEnd(static_cast<int>(dwCompCharOffset + dwCompCharLen), nLineIndex);
SetSelection(ptNewStart, ptNewEnd);
SetCursorPos(ptNewStart);
EnsureVisible(ptNewStart);
UpdateCompositionWindowPos();

return TRUE;
}

case IMR_DOCUMENTFEED:
{
CEPoint ptCursor = GetCursorPos();
return BuildReconvertString(
reinterpret_cast<RECONVERTSTRING*>(lParam), ptCursor.y, static_cast<DWORD>(ptCursor.x), 0);
}
}

Check notice

Code scanning / CodeQL

Long switch case Note

Switch has at least one case that is too long:
5 (39 lines)
.

Copilot Autofix

AI 17 days ago

To fix the problem, we should extract the body of the long IMR_CONFIRMRECONVERTSTRING case into a dedicated helper method on CCrystalTextView. The switch will then simply call this helper and return its result, keeping the case short while preserving all existing behavior.

Concretely:

  • Introduce a private (or member‑scope) helper method, e.g. LRESULT CCrystalTextView::HandleImeConfirmReconvertString(LPARAM lParam), near the existing IME-related methods (BuildReconvertString, OnImeRequest) to keep related logic together.
  • Move all of the logic currently inside case IMR_CONFIRMRECONVERTSTRING: (lines 6878–6913) into this new method, adjusting the early return statements to behave exactly as before.
  • In the switch (wParam) inside OnImeRequest, replace the long case body with a single return HandleImeConfirmReconvertString(lParam);.
  • No new headers or external libraries are needed; we only use existing types and methods (RECONVERTSTRING, CEPoint, GetCursorPos, IsSelection, GetSelection, GetLineLength, SetSelection, SetCursorPos, EnsureVisible, UpdateCompositionWindowPos).

This change keeps the functionality identical, shortens the long case, and improves readability.

Suggested changeset 1
Externals/crystaledit/editlib/ccrystaltextview.cpp

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Externals/crystaledit/editlib/ccrystaltextview.cpp b/Externals/crystaledit/editlib/ccrystaltextview.cpp
--- a/Externals/crystaledit/editlib/ccrystaltextview.cpp
+++ b/Externals/crystaledit/editlib/ccrystaltextview.cpp
@@ -6874,45 +6874,8 @@
       }
 
     case IMR_CONFIRMRECONVERTSTRING:
-      {
-        RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
-        if (pReconv == nullptr)
-          return FALSE;
+      return HandleImeConfirmReconvertString(lParam);
 
-        DWORD dwCompCharOffset = pReconv->dwCompStrOffset / sizeof(tchar_t);
-        DWORD dwCompCharLen = pReconv->dwCompStrLen;
-
-        CEPoint ptCursor = GetCursorPos();
-        int nLineIndex = ptCursor.y;
-
-        if (IsSelection())
-          {
-            CEPoint ptSelStart, ptSelEnd;
-            GetSelection(ptSelStart, ptSelEnd);
-            if (ptSelStart.y != ptSelEnd.y)
-                return FALSE;  // Multi-line selection not supported
-            nLineIndex = ptSelStart.y;
-          }
-
-        // IME adjusted the range but resulted in zero length - reject this
-        int nLineLen = GetLineLength(nLineIndex);
-        if (dwCompCharLen == 0)
-            return FALSE;
-        if (dwCompCharOffset >= static_cast<DWORD>(nLineLen))
-            return FALSE;
-        if (dwCompCharOffset + dwCompCharLen > static_cast<DWORD>(nLineLen))
-            return FALSE;
-
-        CEPoint ptNewStart(static_cast<int>(dwCompCharOffset), nLineIndex);
-        CEPoint ptNewEnd(static_cast<int>(dwCompCharOffset + dwCompCharLen), nLineIndex);
-        SetSelection(ptNewStart, ptNewEnd);
-        SetCursorPos(ptNewStart);
-        EnsureVisible(ptNewStart);
-        UpdateCompositionWindowPos();
-
-        return TRUE;
-      }
-
     case IMR_DOCUMENTFEED:
       {
         CEPoint ptCursor = GetCursorPos();
@@ -6924,6 +6886,46 @@
   return 0;  // Should not reach here
 }
 
+LRESULT CCrystalTextView::HandleImeConfirmReconvertString(LPARAM lParam)
+{
+  RECONVERTSTRING* pReconv = reinterpret_cast<RECONVERTSTRING*>(lParam);
+  if (pReconv == nullptr)
+    return FALSE;
+
+  DWORD dwCompCharOffset = pReconv->dwCompStrOffset / sizeof(tchar_t);
+  DWORD dwCompCharLen = pReconv->dwCompStrLen;
+
+  CEPoint ptCursor = GetCursorPos();
+  int nLineIndex = ptCursor.y;
+
+  if (IsSelection())
+    {
+      CEPoint ptSelStart, ptSelEnd;
+      GetSelection(ptSelStart, ptSelEnd);
+      if (ptSelStart.y != ptSelEnd.y)
+          return FALSE;  // Multi-line selection not supported
+      nLineIndex = ptSelStart.y;
+    }
+
+  // IME adjusted the range but resulted in zero length - reject this
+  int nLineLen = GetLineLength(nLineIndex);
+  if (dwCompCharLen == 0)
+      return FALSE;
+  if (dwCompCharOffset >= static_cast<DWORD>(nLineLen))
+      return FALSE;
+  if (dwCompCharOffset + dwCompCharLen > static_cast<DWORD>(nLineLen))
+      return FALSE;
+
+  CEPoint ptNewStart(static_cast<int>(dwCompCharOffset), nLineIndex);
+  CEPoint ptNewEnd(static_cast<int>(dwCompCharOffset + dwCompCharLen), nLineIndex);
+  SetSelection(ptNewStart, ptNewEnd);
+  SetCursorPos(ptNewStart);
+  EnsureVisible(ptNewStart);
+  UpdateCompositionWindowPos();
+
+  return TRUE;
+}
+
 void CCrystalTextView::OnEditDeleteBack() 
 {
   if( !m_bIncrementalSearchForward && !m_bIncrementalSearchBackward )
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants