terminal/src/tsf/TfEditSession.cpp
Taehyeok Kang 08646e5ca3
Fix Korean IME to display a character being composed in conhost (#8632)
## Summary of the Pull Request
This PR fixes Korean IME to display a character being composed in conhost.

### Before
![before](https://user-images.githubusercontent.com/58393346/102745310-03f23c80-439f-11eb-9f86-263da2dbddbb.gif)

### After
![after](https://user-images.githubusercontent.com/58393346/102745343-14a2b280-439f-11eb-853c-42b52bf442f4.gif)

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #6227
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-01-04 21:05:24 +00:00

1168 lines
38 KiB
C++

/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
Module Name:
TfEditSession.cpp
Abstract:
This file implements the CEditSessionObject Class.
Author:
Revision History:
Notes:
--*/
#include "precomp.h"
#include "TfConvArea.h"
#include "TfCatUtil.h"
#include "TfDispAttr.h"
#include "TfEditSession.h"
//+---------------------------------------------------------------------------
//
// CEditSessionObject::IUnknown::QueryInterface
// CEditSessionObject::IUnknown::AddRef
// CEditSessionObject::IUnknown::Release
//
//----------------------------------------------------------------------------
STDAPI CEditSessionObject::QueryInterface(REFIID riid, void** ppvObj)
{
*ppvObj = nullptr;
if (IsEqualIID(riid, IID_ITfEditSession))
{
*ppvObj = static_cast<ITfEditSession*>(this);
}
else if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObj = static_cast<IUnknown*>(this);
}
if (*ppvObj)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDAPI_(ULONG)
CEditSessionObject::AddRef()
{
return ++m_cRef;
}
STDAPI_(ULONG)
CEditSessionObject::Release()
{
long cr;
cr = --m_cRef;
FAIL_FAST_IF(!(cr >= 0));
if (cr == 0)
{
delete this;
}
return cr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::GetAllTextRange
//
//----------------------------------------------------------------------------
// static
[[nodiscard]] HRESULT CEditSessionObject::GetAllTextRange(TfEditCookie ec,
ITfContext* ic,
ITfRange** range,
LONG* lpTextLength,
TF_HALTCOND* lpHaltCond)
{
HRESULT hr;
//
// init lpTextLength first.
//
*lpTextLength = 0;
//
// Create the range that covers all the text.
//
wil::com_ptr_nothrow<ITfRange> rangeFull;
if (FAILED(hr = ic->GetStart(ec, &rangeFull)))
{
return hr;
}
LONG cch = 0;
if (FAILED(hr = rangeFull->ShiftEnd(ec, LONG_MAX, &cch, lpHaltCond)))
{
return hr;
}
if (FAILED(hr = rangeFull->Clone(range)))
{
return hr;
}
*lpTextLength = cch;
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::SetTextInRange
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::SetTextInRange(TfEditCookie ec,
ITfRange* range,
__in_ecount_opt(len) LPWSTR psz,
DWORD len)
{
HRESULT hr = E_FAIL;
if (g_pConsoleTSF)
{
g_pConsoleTSF->SetModifyingDocFlag(TRUE);
hr = range->SetText(ec, 0, psz, len);
g_pConsoleTSF->SetModifyingDocFlag(FALSE);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::ClearTextInRange
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::ClearTextInRange(TfEditCookie ec, ITfRange* range)
{
//
// Clear the text in Cicero TOM
//
return SetTextInRange(ec, range, nullptr, 0);
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::_GetCursorPosition
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::_GetCursorPosition(TfEditCookie ec, CCompCursorPos& CompCursorPos)
{
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
if (pic == nullptr)
{
return E_FAIL;
}
HRESULT hr;
ULONG cFetched;
TF_SELECTION sel;
sel.range = nullptr;
if (SUCCEEDED(hr = pic->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &cFetched)))
{
wil::com_ptr_nothrow<ITfRange> start;
LONG ich;
TF_HALTCOND hc;
hc.pHaltRange = sel.range;
hc.aHaltPos = (sel.style.ase == TF_AE_START) ? TF_ANCHOR_START : TF_ANCHOR_END;
hc.dwFlags = 0;
if (SUCCEEDED(hr = GetAllTextRange(ec, pic, &start, &ich, &hc)))
{
CompCursorPos.SetCursorPosition(ich);
}
SafeReleaseClear(sel.range);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::_GetTextAndAttribute
//
//----------------------------------------------------------------------------
//
// Get text and attribute in given range
//
// ITfRange::range
// TF_ANCHOR_START
// |======================================================================|
// +--------------------+ #+----------+
// |ITfRange::pPropRange| #|pPropRange|
// +--------------------+ #+----------+
// | GUID_ATOM | #
// +--------------------+ #
// ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^#
// ITfRange::gap_range gap_range #
// #
// V
// ITfRange::no_display_attribute_range
// result_comp
// +1 <- 0 -> -1
//
[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttribute(TfEditCookie ec,
ITfRange* rangeIn,
std::wstring& CompStr,
std::vector<TfGuidAtom>& CompGuid,
std::wstring& ResultStr,
BOOL bInWriteSession,
CicCategoryMgr* pCicCatMgr,
CicDisplayAttributeMgr* pCicDispAttr)
{
HRESULT hr;
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
if (pic == nullptr)
{
return E_FAIL;
}
//
// Get no display attribute range if there exist.
// Otherwise, result range is the same to input range.
//
LONG result_comp;
wil::com_ptr_nothrow<ITfRange> no_display_attribute_range;
if (FAILED(hr = rangeIn->Clone(&no_display_attribute_range)))
{
return hr;
}
const GUID* guids[] = { &GUID_PROP_COMPOSING };
const int guid_size = sizeof(guids) / sizeof(GUID*);
if (FAILED(hr = _GetNoDisplayAttributeRange(ec, rangeIn, guids, guid_size, no_display_attribute_range.get())))
{
return hr;
}
wil::com_ptr_nothrow<ITfReadOnlyProperty> propComp;
if (FAILED(hr = pic->TrackProperties(guids, guid_size, // system property
NULL,
0, // application property
&propComp)))
{
return hr;
}
wil::com_ptr_nothrow<IEnumTfRanges> enumComp;
if (FAILED(hr = propComp->EnumRanges(ec, &enumComp, rangeIn)))
{
return hr;
}
wil::com_ptr_nothrow<ITfRange> range;
while (enumComp->Next(1, &range, nullptr) == S_OK)
{
VARIANT var;
BOOL fCompExist = FALSE;
hr = propComp->GetValue(ec, range.get(), &var);
if (S_OK == hr)
{
wil::com_ptr_nothrow<IEnumTfPropertyValue> EnumPropVal;
if (wil::try_com_query_to(var.punkVal, &EnumPropVal))
{
TF_PROPERTYVAL tfPropertyVal;
while (EnumPropVal->Next(1, &tfPropertyVal, nullptr) == S_OK)
{
for (int i = 0; i < guid_size; i++)
{
if (IsEqualGUID(tfPropertyVal.guidId, *guids[i]))
{
if ((V_VT(&tfPropertyVal.varValue) == VT_I4 && V_I4(&tfPropertyVal.varValue) != 0))
{
fCompExist = TRUE;
break;
}
}
}
VariantClear(&tfPropertyVal.varValue);
if (fCompExist)
{
break;
}
}
}
}
VariantClear(&var);
ULONG ulNumProp;
wil::com_ptr_nothrow<IEnumTfRanges> enumProp;
wil::com_ptr_nothrow<ITfReadOnlyProperty> prop;
if (FAILED(hr = pCicDispAttr->GetDisplayAttributeTrackPropertyRange(ec, pic, range.get(), &prop, &enumProp, &ulNumProp)))
{
return hr;
}
// use text range for get text
wil::com_ptr_nothrow<ITfRange> textRange;
if (FAILED(hr = range->Clone(&textRange)))
{
return hr;
}
// use text range for gap text (no property range).
wil::com_ptr_nothrow<ITfRange> gap_range;
if (FAILED(hr = range->Clone(&gap_range)))
{
return hr;
}
wil::com_ptr_nothrow<ITfRange> pPropRange;
while (enumProp->Next(1, &pPropRange, nullptr) == S_OK)
{
// pick up the gap up to the next property
gap_range->ShiftEndToRange(ec, pPropRange.get(), TF_ANCHOR_START);
//
// GAP range
//
no_display_attribute_range->CompareStart(ec, gap_range.get(), TF_ANCHOR_START, &result_comp);
LOG_IF_FAILED(_GetTextAndAttributeGapRange(ec,
gap_range.get(),
result_comp,
CompStr,
CompGuid,
ResultStr));
//
// Get display attribute data if some GUID_ATOM exist.
//
TF_DISPLAYATTRIBUTE da;
TfGuidAtom guidatom = TF_INVALID_GUIDATOM;
LOG_IF_FAILED(pCicDispAttr->GetDisplayAttributeData(pCicCatMgr->GetCategoryMgr(),
ec,
prop.get(),
pPropRange.get(),
&da,
&guidatom,
ulNumProp));
//
// Property range
//
no_display_attribute_range->CompareStart(ec, pPropRange.get(), TF_ANCHOR_START, &result_comp);
// Adjust GAP range's start anchor to the end of property range.
gap_range->ShiftStartToRange(ec, pPropRange.get(), TF_ANCHOR_END);
//
// Get property text
//
LOG_IF_FAILED(_GetTextAndAttributePropertyRange(ec,
pPropRange.get(),
fCompExist,
result_comp,
bInWriteSession,
da,
guidatom,
CompStr,
CompGuid,
ResultStr));
} // while
// the last non-attr
textRange->ShiftStartToRange(ec, gap_range.get(), TF_ANCHOR_START);
textRange->ShiftEndToRange(ec, range.get(), TF_ANCHOR_END);
BOOL fEmpty;
while (textRange->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
{
WCHAR wstr0[256 + 1];
ULONG ulcch0 = ARRAYSIZE(wstr0) - 1;
textRange->GetText(ec, TF_TF_MOVESTART, wstr0, ulcch0, &ulcch0);
TfGuidAtom guidatom;
guidatom = TF_INVALID_GUIDATOM;
TF_DISPLAYATTRIBUTE da;
da.bAttr = TF_ATTR_INPUT;
try
{
CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
CompStr.append(wstr0, ulcch0);
}
CATCH_RETURN();
}
textRange->Collapse(ec, TF_ANCHOR_END);
} // out-most while for GUID_PROP_COMPOSING
//
// set GUID_PROP_CONIME_TRACKCOMPOSITION
//
wil::com_ptr_nothrow<ITfProperty> PropertyTrackComposition;
if (SUCCEEDED(hr = pic->GetProperty(GUID_PROP_CONIME_TRACKCOMPOSITION, &PropertyTrackComposition)))
{
VARIANT var;
var.vt = VT_I4;
var.lVal = 1;
PropertyTrackComposition->SetValue(ec, rangeIn, &var);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::_GetTextAndAttributeGapRange
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttributeGapRange(TfEditCookie ec,
ITfRange* gap_range,
LONG result_comp,
std::wstring& CompStr,
std::vector<TfGuidAtom>& CompGuid,
std::wstring& ResultStr)
{
TfGuidAtom guidatom;
guidatom = TF_INVALID_GUIDATOM;
TF_DISPLAYATTRIBUTE da;
da.bAttr = TF_ATTR_INPUT;
BOOL fEmpty;
WCHAR wstr0[256 + 1];
ULONG ulcch0;
while (gap_range->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
{
wil::com_ptr_nothrow<ITfRange> backup_range;
if (FAILED(gap_range->Clone(&backup_range)))
{
return E_FAIL;
}
//
// Retrieve gap text if there exist.
//
ulcch0 = ARRAYSIZE(wstr0) - 1;
if (FAILED(gap_range->GetText(ec,
TF_TF_MOVESTART, // Move range to next after get text.
wstr0,
ulcch0,
&ulcch0)))
{
return E_FAIL;
}
try
{
if (result_comp <= 0)
{
CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
CompStr.append(wstr0, ulcch0);
}
else
{
ResultStr.append(wstr0, ulcch0);
LOG_IF_FAILED(ClearTextInRange(ec, backup_range.get()));
}
}
CATCH_RETURN();
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::_GetTextAndAttributePropertyRange
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::_GetTextAndAttributePropertyRange(TfEditCookie ec,
ITfRange* pPropRange,
BOOL fCompExist,
LONG result_comp,
BOOL bInWriteSession,
TF_DISPLAYATTRIBUTE da,
TfGuidAtom guidatom,
std::wstring& CompStr,
std::vector<TfGuidAtom>& CompGuid,
std::wstring& ResultStr)
{
BOOL fEmpty;
WCHAR wstr0[256 + 1];
ULONG ulcch0;
while (pPropRange->IsEmpty(ec, &fEmpty) == S_OK && !fEmpty)
{
wil::com_ptr_nothrow<ITfRange> backup_range;
if (FAILED(pPropRange->Clone(&backup_range)))
{
return E_FAIL;
}
//
// Retrieve property text if there exist.
//
ulcch0 = ARRAYSIZE(wstr0) - 1;
if (FAILED(pPropRange->GetText(ec,
TF_TF_MOVESTART, // Move range to next after get text.
wstr0,
ulcch0,
&ulcch0)))
{
return E_FAIL;
}
try
{
// see if there is a valid disp attribute
if (fCompExist == TRUE && result_comp <= 0)
{
if (guidatom == TF_INVALID_GUIDATOM)
{
da.bAttr = TF_ATTR_INPUT;
}
CompGuid.insert(CompGuid.end(), ulcch0, guidatom);
CompStr.append(wstr0, ulcch0);
}
else if (bInWriteSession)
{
// if there's no disp attribute attached, it probably means
// the part of string is finalized.
//
ResultStr.append(wstr0, ulcch0);
// it was a 'determined' string
// so the doc has to shrink
//
LOG_IF_FAILED(ClearTextInRange(ec, backup_range.get()));
}
else
{
//
// Prevent infinite loop
//
break;
}
}
CATCH_RETURN();
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionObject::_GetNoDisplayAttributeRange
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionObject::_GetNoDisplayAttributeRange(TfEditCookie ec,
ITfRange* rangeIn,
const GUID** guids,
const int guid_size,
ITfRange* no_display_attribute_range)
{
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
if (pic == nullptr)
{
return E_FAIL;
}
wil::com_ptr_nothrow<ITfReadOnlyProperty> propComp;
HRESULT hr = pic->TrackProperties(guids, guid_size, // system property
nullptr,
0, // application property
&propComp);
if (FAILED(hr))
{
return hr;
}
wil::com_ptr_nothrow<IEnumTfRanges> enumComp;
hr = propComp->EnumRanges(ec, &enumComp, rangeIn);
if (FAILED(hr))
{
return hr;
}
wil::com_ptr_nothrow<ITfRange> pRange;
while (enumComp->Next(1, &pRange, nullptr) == S_OK)
{
VARIANT var;
BOOL fCompExist = FALSE;
hr = propComp->GetValue(ec, pRange.get(), &var);
if (S_OK == hr)
{
wil::com_ptr_nothrow<IEnumTfPropertyValue> EnumPropVal;
if (wil::try_com_query_to(var.punkVal, &EnumPropVal))
{
TF_PROPERTYVAL tfPropertyVal;
while (EnumPropVal->Next(1, &tfPropertyVal, nullptr) == S_OK)
{
for (int i = 0; i < guid_size; i++)
{
if (IsEqualGUID(tfPropertyVal.guidId, *guids[i]))
{
if ((V_VT(&tfPropertyVal.varValue) == VT_I4 && V_I4(&tfPropertyVal.varValue) != 0))
{
fCompExist = TRUE;
break;
}
}
}
VariantClear(&tfPropertyVal.varValue);
if (fCompExist)
{
break;
}
}
}
}
if (!fCompExist)
{
// Adjust GAP range's start anchor to the end of property range.
no_display_attribute_range->ShiftStartToRange(ec, pRange.get(), TF_ANCHOR_START);
}
VariantClear(&var);
}
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionCompositionComplete::CompComplete
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionCompositionComplete::CompComplete(TfEditCookie ec)
{
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
RETURN_HR_IF_NULL(E_FAIL, pic);
// Get the whole text, finalize it, and set empty string in TOM
wil::com_ptr_nothrow<ITfRange> spRange;
LONG cch;
RETURN_IF_FAILED(GetAllTextRange(ec, pic, &spRange, &cch));
// Check if a part of the range has already been finalized but not removed yet.
// Adjust the range appropriately to avoid inserting the same text twice.
long cchCompleted = g_pConsoleTSF->GetCompletedRangeLength();
if ((cchCompleted > 0) &&
(cchCompleted < cch) &&
SUCCEEDED(spRange->ShiftStart(ec, cchCompleted, &cchCompleted, NULL)))
{
FAIL_FAST_IF(!((cchCompleted > 0) && (cchCompleted < cch)));
cch -= cchCompleted;
}
else
{
cchCompleted = 0;
}
// Get conversion area service.
CConversionArea* conv_area = g_pConsoleTSF->GetConversionArea();
RETURN_HR_IF_NULL(E_FAIL, conv_area);
// If there is no string in TextStore we don't have to do anything.
if (!cch)
{
// Clear composition
LOG_IF_FAILED(conv_area->ClearComposition());
return S_OK;
}
HRESULT hr = S_OK;
try
{
auto wstr = std::make_unique<WCHAR[]>(cch + 1);
// Get the whole text, finalize it, and erase the whole text.
if (SUCCEEDED(spRange->GetText(ec, TF_TF_IGNOREEND, wstr.get(), (ULONG)cch, (ULONG*)&cch)))
{
// Make Result String.
hr = conv_area->DrawResult({ wstr.get(), static_cast<size_t>(cch) });
}
}
CATCH_RETURN();
// Update the stored length of the completed fragment.
g_pConsoleTSF->SetCompletedRangeLength(cchCompleted + cch);
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionCompositionCleanup::EmptyCompositionRange()
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionCompositionCleanup::EmptyCompositionRange(TfEditCookie ec)
{
if (!g_pConsoleTSF)
{
return E_FAIL;
}
if (!g_pConsoleTSF->IsPendingCompositionCleanup())
{
return S_OK;
}
HRESULT hr = E_FAIL;
ITfContext* pic = g_pConsoleTSF->GetInputContext();
if (pic != nullptr)
{
// Cleanup (empty the context range) after the last composition.
hr = S_OK;
long cchCompleted = g_pConsoleTSF->GetCompletedRangeLength();
if (cchCompleted != 0)
{
wil::com_ptr_nothrow<ITfRange> spRange;
LONG cch;
hr = GetAllTextRange(ec, pic, &spRange, &cch);
if (SUCCEEDED(hr))
{
// Clean up only the completed part (which start is expected to coincide with the start of the full range).
if (cchCompleted < cch)
{
spRange->ShiftEnd(ec, (cchCompleted - cch), &cch, nullptr);
}
hr = ClearTextInRange(ec, spRange.get());
g_pConsoleTSF->SetCompletedRangeLength(0); // cleaned up all completed text
}
}
}
g_pConsoleTSF->OnCompositionCleanup(SUCCEEDED(hr));
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionUpdateCompositionString::UpdateCompositionString
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::UpdateCompositionString(TfEditCookie ec)
{
HRESULT hr;
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
if (pic == nullptr)
{
return E_FAIL;
}
// Reset the 'edit session requested' flag.
g_pConsoleTSF->OnEditSession();
// If the composition has been cancelled\finalized, no update necessary.
if (!g_pConsoleTSF->IsInComposition())
{
return S_OK;
}
BOOL bInWriteSession;
if (FAILED(hr = pic->InWriteSession(g_pConsoleTSF->GetTfClientId(), &bInWriteSession)))
{
return hr;
}
wil::com_ptr_nothrow<ITfRange> FullTextRange;
LONG lTextLength;
if (FAILED(hr = GetAllTextRange(ec, pic, &FullTextRange, &lTextLength)))
{
return hr;
}
wil::com_ptr_nothrow<ITfRange> InterimRange;
BOOL fInterim = FALSE;
if (FAILED(hr = _IsInterimSelection(ec, &InterimRange, &fInterim)))
{
return hr;
}
CicCategoryMgr* pCicCat = nullptr;
CicDisplayAttributeMgr* pDispAttr = nullptr;
//
// Create Cicero Category Manager and Display Attribute Manager
//
hr = _CreateCategoryAndDisplayAttributeManager(&pCicCat, &pDispAttr);
if (SUCCEEDED(hr))
{
if (fInterim)
{
hr = _MakeInterimString(ec,
FullTextRange.get(),
InterimRange.get(),
lTextLength,
bInWriteSession,
pCicCat,
pDispAttr);
}
else
{
hr = _MakeCompositionString(ec, FullTextRange.get(), bInWriteSession, pCicCat, pDispAttr);
}
}
if (pCicCat)
{
delete pCicCat;
}
if (pDispAttr)
{
delete pDispAttr;
}
return hr;
}
//+---------------------------------------------------------------------------
//
// CEditSessionUpdateCompositionString::_IsInterimSelection
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_IsInterimSelection(TfEditCookie ec,
ITfRange** pInterimRange,
BOOL* pfInterim)
{
ITfContext* pic = g_pConsoleTSF ? g_pConsoleTSF->GetInputContext() : nullptr;
if (pic == nullptr)
{
return E_FAIL;
}
ULONG cFetched;
TF_SELECTION sel;
sel.range = nullptr;
*pfInterim = FALSE;
if (pic->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &sel, &cFetched) != S_OK)
{
// no selection. we can return S_OK.
return S_OK;
}
if (sel.style.fInterimChar && sel.range)
{
HRESULT hr;
if (FAILED(hr = sel.range->Clone(pInterimRange)))
{
SafeReleaseClear(sel.range);
return hr;
}
*pfInterim = TRUE;
}
SafeReleaseClear(sel.range);
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionUpdateCompositionString::_MakeCompositionString
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_MakeCompositionString(TfEditCookie ec,
ITfRange* FullTextRange,
BOOL bInWriteSession,
CicCategoryMgr* pCicCatMgr,
CicDisplayAttributeMgr* pCicDispAttr)
{
std::wstring CompStr;
std::vector<TfGuidAtom> CompGuid;
CCompCursorPos CompCursorPos;
std::wstring ResultStr;
BOOL fIgnorePreviousCompositionResult = FALSE;
RETURN_IF_FAILED(_GetTextAndAttribute(ec,
FullTextRange,
CompStr,
CompGuid,
ResultStr,
bInWriteSession,
pCicCatMgr,
pCicDispAttr));
if (g_pConsoleTSF && g_pConsoleTSF->IsPendingCompositionCleanup())
{
// Don't draw the previous composition result if there was a cleanup session requested for it.
fIgnorePreviousCompositionResult = TRUE;
// Cancel pending cleanup, since the ResultStr was cleared from the composition in _GetTextAndAttribute.
g_pConsoleTSF->OnCompositionCleanup(TRUE);
}
RETURN_IF_FAILED(_GetCursorPosition(ec, CompCursorPos));
// Get display attribute manager
ITfDisplayAttributeMgr* dam = pCicDispAttr->GetDisplayAttributeMgr();
RETURN_HR_IF_NULL(E_FAIL, dam);
// Get category manager
ITfCategoryMgr* cat = pCicCatMgr->GetCategoryMgr();
RETURN_HR_IF_NULL(E_FAIL, cat);
// Allocate and fill TF_DISPLAYATTRIBUTE
try
{
// Get conversion area service.
CConversionArea* conv_area = g_pConsoleTSF ? g_pConsoleTSF->GetConversionArea() : nullptr;
RETURN_HR_IF_NULL(E_FAIL, conv_area);
if (!ResultStr.empty() && !fIgnorePreviousCompositionResult)
{
return conv_area->DrawResult(ResultStr);
}
if (!CompStr.empty())
{
const size_t cchDisplayAttribute = CompGuid.size();
std::vector<TF_DISPLAYATTRIBUTE> DisplayAttributes;
DisplayAttributes.reserve(cchDisplayAttribute);
for (size_t i = 0; i < cchDisplayAttribute; i++)
{
TF_DISPLAYATTRIBUTE da;
ZeroMemory(&da, sizeof(da));
da.bAttr = TF_ATTR_OTHER;
GUID guid;
if (SUCCEEDED(cat->GetGUID(CompGuid.at(i), &guid)))
{
CLSID clsid;
wil::com_ptr_nothrow<ITfDisplayAttributeInfo> dai;
if (SUCCEEDED(dam->GetDisplayAttributeInfo(guid, &dai, &clsid)))
{
dai->GetAttributeInfo(&da);
}
}
DisplayAttributes.emplace_back(da);
}
return conv_area->DrawComposition(CompStr, // composition string
DisplayAttributes, // display attributes
CompCursorPos.GetCursorPosition()); // cursor position
}
}
CATCH_RETURN();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionUpdateCompositionString::_MakeInterimString
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_MakeInterimString(TfEditCookie ec,
ITfRange* FullTextRange,
ITfRange* InterimRange,
LONG lTextLength,
BOOL bInWriteSession,
CicCategoryMgr* pCicCatMgr,
CicDisplayAttributeMgr* pCicDispAttr)
{
LONG lStartResult;
LONG lEndResult;
FullTextRange->CompareStart(ec, InterimRange, TF_ANCHOR_START, &lStartResult);
RETURN_HR_IF(E_FAIL, lStartResult > 0);
FullTextRange->CompareEnd(ec, InterimRange, TF_ANCHOR_END, &lEndResult);
RETURN_HR_IF(E_FAIL, lEndResult < 0);
if (lStartResult < 0)
{
// Make result string.
RETURN_IF_FAILED(FullTextRange->ShiftEndToRange(ec, InterimRange, TF_ANCHOR_START));
// Interim char assume 1 char length.
// Full text length - 1 means result string length.
lTextLength--;
FAIL_FAST_IF(!(lTextLength > 0));
if (lTextLength > 0)
{
try
{
auto wstr = std::make_unique<WCHAR[]>(lTextLength + 1);
// Get the result text, finalize it, and erase the result text.
if (SUCCEEDED(FullTextRange->GetText(ec, TF_TF_IGNOREEND, wstr.get(), (ULONG)lTextLength, (ULONG*)&lTextLength)))
{
// Clear the TOM
LOG_IF_FAILED(ClearTextInRange(ec, FullTextRange));
}
}
CATCH_RETURN();
}
}
// Make interim character
std::wstring CompStr;
std::vector<TfGuidAtom> CompGuid;
std::wstring _tempResultStr;
RETURN_IF_FAILED(_GetTextAndAttribute(ec,
InterimRange,
CompStr,
CompGuid,
_tempResultStr,
bInWriteSession,
pCicCatMgr,
pCicDispAttr));
// Get display attribute manager
ITfDisplayAttributeMgr* dam = pCicDispAttr->GetDisplayAttributeMgr();
RETURN_HR_IF_NULL(E_FAIL, dam);
// Get category manager
ITfCategoryMgr* cat = pCicCatMgr->GetCategoryMgr();
RETURN_HR_IF_NULL(E_FAIL, cat);
// Allocate and fill TF_DISPLAYATTRIBUTE
try
{
// Get conversion area service.
CConversionArea* conv_area = g_pConsoleTSF ? g_pConsoleTSF->GetConversionArea() : nullptr;
RETURN_HR_IF_NULL(E_FAIL, conv_area);
if (!CompStr.empty())
{
const size_t cchDisplayAttribute = CompGuid.size();
std::vector<TF_DISPLAYATTRIBUTE> DisplayAttributes;
DisplayAttributes.reserve(cchDisplayAttribute);
for (size_t i = 0; i < cchDisplayAttribute; i++)
{
TF_DISPLAYATTRIBUTE da;
ZeroMemory(&da, sizeof(da));
da.bAttr = TF_ATTR_OTHER;
GUID guid;
if (SUCCEEDED(cat->GetGUID(CompGuid.at(i), &guid)))
{
CLSID clsid;
wil::com_ptr_nothrow<ITfDisplayAttributeInfo> dai;
if (SUCCEEDED(dam->GetDisplayAttributeInfo(guid, &dai, &clsid)))
{
dai->GetAttributeInfo(&da);
}
}
DisplayAttributes.emplace_back(da);
}
return conv_area->DrawComposition(CompStr, // composition string (Interim string)
DisplayAttributes); // display attributes
}
}
CATCH_RETURN();
return S_OK;
}
//+---------------------------------------------------------------------------
//
// CEditSessionUpdateCompositionString::_CreateCategoryAndDisplayAttributeManager
//
//----------------------------------------------------------------------------
[[nodiscard]] HRESULT CEditSessionUpdateCompositionString::_CreateCategoryAndDisplayAttributeManager(
CicCategoryMgr** pCicCatMgr,
CicDisplayAttributeMgr** pCicDispAttr)
{
HRESULT hr = E_OUTOFMEMORY;
CicCategoryMgr* pTmpCat = nullptr;
CicDisplayAttributeMgr* pTmpDispAttr = nullptr;
//
// Create Cicero Category Manager
//
pTmpCat = new (std::nothrow) CicCategoryMgr;
if (pTmpCat)
{
if (SUCCEEDED(hr = pTmpCat->InitCategoryInstance()))
{
ITfCategoryMgr* pcat = pTmpCat->GetCategoryMgr();
if (pcat)
{
//
// Create Cicero Display Attribute Manager
//
pTmpDispAttr = new (std::nothrow) CicDisplayAttributeMgr;
if (pTmpDispAttr)
{
if (SUCCEEDED(hr = pTmpDispAttr->InitDisplayAttributeInstance(pcat)))
{
*pCicCatMgr = pTmpCat;
*pCicDispAttr = pTmpDispAttr;
}
}
}
}
}
if (FAILED(hr))
{
if (pTmpCat)
{
delete pTmpCat;
}
if (pTmpDispAttr)
{
delete pTmpDispAttr;
}
}
return hr;
}