08646e5ca3
## 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
1168 lines
38 KiB
C++
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;
|
|
}
|