richedit: WM_SETTEXT must immediately autodetect URLs, without waiting for a WM_CHAR.

This commit is contained in:
Alex Villacís Lasso 2008-06-18 10:54:17 -05:00 committed by Alexandre Julliard
parent 60c42bd3db
commit 38d7ba6eff
3 changed files with 410 additions and 10 deletions

View File

@ -2454,6 +2454,22 @@ static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
}
else
TRACE("WM_SETTEXT - NULL\n");
if (editor->AutoURLDetect_bEnable)
{
int cMin = 0, cMax = -1;
while (ME_FindNextURLCandidate(editor, cMin, cMax, &cMin, &cMax))
{
if (ME_IsCandidateAnURL(editor, cMin, cMax)) {
CHARFORMAT2W link;
link.cbSize = sizeof(link);
link.dwMask = CFM_LINK;
link.dwEffects = CFE_LINK;
ME_SetCharFormat(editor, cMin, cMax - cMin, &link);
}
cMin = cMax;
cMax = -1;
}
}
ME_SetSelection(editor, 0, 0);
editor->nModifyStep = 0;
ME_CommitUndo(editor);
@ -3449,9 +3465,11 @@ int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int nStart, int nChars, in
CopyMemory(buffer, item->member.run.strText->szData + nStart, sizeof(WCHAR)*nLen);
nChars -= nLen;
nWritten += nLen;
if (!nChars)
return nWritten;
buffer += nLen;
if (!nChars) {
*buffer = 0;
return nWritten;
}
nStart = 0;
item = ME_FindItemFwd(item, diRun);
}
@ -3713,3 +3731,159 @@ int ME_AutoURLDetect(ME_TextEditor *editor, WCHAR curChar)
}
return 0;
}
static BOOL isurlspecial(WCHAR c)
{
static const WCHAR special_chars[] = {'.','/','%','@','*','|','\\','+','#',0};
return strchrW( special_chars, c ) != NULL;
}
/**
* This proc takes a selection, and scans it forward in order to select the span
* of a possible URL candidate. A possible URL candidate must start with isalnum
* or one of the following special characters: *|/\+%#@ and must consist entirely
* of the characters allowed to start the URL, plus : (colon) which may occur
* at most once, and not at either end.
*
* sel_max == -1 indicates scan to end of text.
*/
BOOL ME_FindNextURLCandidate(ME_TextEditor *editor, int sel_min, int sel_max,
int * candidate_min, int * candidate_max)
{
ME_DisplayItem * item;
ME_DisplayItem * para;
int nStart;
BOOL foundColon = FALSE;
WCHAR lastAcceptedChar = '\0';
TRACE("sel_min = %d sel_max = %d\n", sel_min, sel_max);
*candidate_min = *candidate_max = -1;
item = ME_FindItemAtOffset(editor, diRun, sel_min, &nStart);
if (!item) return FALSE;
TRACE("nStart = %d\n", nStart);
para = ME_GetParagraph(item);
if (sel_max == -1) sel_max = ME_GetTextLength(editor);
while (item && para->member.para.nCharOfs + item->member.run.nCharOfs + nStart < sel_max)
{
ME_DisplayItem * next_item;
if (!(item->member.run.nFlags & MERF_ENDPARA)) {
/* Find start of candidate */
if (*candidate_min == -1) {
while (nStart < ME_StrLen(item->member.run.strText) &&
!(isalnumW(item->member.run.strText->szData[nStart]) ||
isurlspecial(item->member.run.strText->szData[nStart]))) {
nStart++;
}
if (nStart < ME_StrLen(item->member.run.strText) &&
(isalnumW(item->member.run.strText->szData[nStart]) ||
isurlspecial(item->member.run.strText->szData[nStart]))) {
*candidate_min = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
lastAcceptedChar = item->member.run.strText->szData[nStart];
nStart++;
}
}
/* Find end of candidate */
if (*candidate_min >= 0) {
while (nStart < ME_StrLen(item->member.run.strText) &&
(isalnumW(item->member.run.strText->szData[nStart]) ||
isurlspecial(item->member.run.strText->szData[nStart]) ||
(!foundColon && item->member.run.strText->szData[nStart] == ':') )) {
if (item->member.run.strText->szData[nStart] == ':') foundColon = TRUE;
lastAcceptedChar = item->member.run.strText->szData[nStart];
nStart++;
}
if (nStart < ME_StrLen(item->member.run.strText) &&
!(isalnumW(item->member.run.strText->szData[nStart]) ||
isurlspecial(item->member.run.strText->szData[nStart]) )) {
*candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
nStart++;
if (lastAcceptedChar == ':') (*candidate_max)--;
return TRUE;
}
}
} else {
/* End of paragraph: skip it if before candidate span, or terminates
current active span */
if (*candidate_min >= 0) {
*candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs;
if (lastAcceptedChar == ':') (*candidate_max)--;
return TRUE;
}
}
/* Reaching this point means no span was found, so get next span */
next_item = ME_FindItemFwd(item, diRun);
if (!next_item) {
if (*candidate_min >= 0) {
/* There are no further runs, so take end of text as end of candidate */
*candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
if (lastAcceptedChar == ':') (*candidate_max)--;
return TRUE;
}
}
item = next_item;
para = ME_GetParagraph(item);
nStart = 0;
}
if (item) {
if (*candidate_min >= 0) {
/* There are no further runs, so take end of text as end of candidate */
*candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
if (lastAcceptedChar == ':') (*candidate_max)--;
return TRUE;
}
}
return FALSE;
}
/**
* This proc evaluates the selection and returns TRUE if it can be considered an URL
*/
BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, int sel_min, int sel_max)
{
struct prefix_s {
const char *text;
int length;
} prefixes[12] = {
/* Code below depends on these being in decreasing length order! */
{"prospero:", 10},
{"telnet:", 8},
{"gopher:", 8},
{"mailto:", 8},
{"https:", 7},
{"file:", 6},
{"news:", 6},
{"wais:", 6},
{"nntp:", 6},
{"http:", 5},
{"www.", 5},
{"ftp:", 5},
};
LPWSTR bufferW = NULL;
WCHAR bufW[32];
int i;
if (sel_max == -1) sel_max = ME_GetTextLength(editor);
assert(sel_min <= sel_max);
for (i = 0; i < sizeof(prefixes) / sizeof(struct prefix_s); i++)
{
if (sel_max - sel_min < prefixes[i].length) continue;
if (bufferW == NULL) {
bufferW = (LPWSTR)heap_alloc((sel_max - sel_min + 1) * sizeof(WCHAR));
}
ME_GetTextW(editor, bufferW, sel_min, min(sel_max - sel_min, strlen(prefixes[i].text)), 0);
MultiByteToWideChar(CP_ACP, 0, prefixes[i].text, -1, bufW, 32);
if (!lstrcmpW(bufW, bufferW))
{
heap_free(bufferW);
return TRUE;
}
}
if (bufferW != NULL) heap_free(bufferW);
return FALSE;
}

View File

@ -279,6 +279,9 @@ void ME_StreamInFill(ME_InStream *stream);
int ME_AutoURLDetect(ME_TextEditor *editor, WCHAR curChar);
extern int me_debug;
extern void DoWrap(ME_TextEditor *editor);
extern BOOL ME_FindNextURLCandidate(ME_TextEditor *editor, int sel_min, int sel_max,
int * candidate_min, int * candidate_max);
extern BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, int sel_min, int sel_max);
/* undo.c */
ME_UndoItem *ME_AddUndoItem(ME_TextEditor *editor, ME_DIType type, const ME_DisplayItem *pdi);

View File

@ -929,14 +929,20 @@ static void test_EM_SETOPTIONS(void)
DestroyWindow(hwndRichEdit);
}
static void check_CFE_LINK_rcvd(HWND hwnd, int is_url, const char * url)
static int check_CFE_LINK_selection(HWND hwnd, int sel_start, int sel_end)
{
CHARFORMAT2W text_format;
int link_present = 0;
text_format.cbSize = sizeof(text_format);
SendMessage(hwnd, EM_SETSEL, 0, 1);
SendMessage(hwnd, EM_SETSEL, sel_start, sel_end);
SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
link_present = text_format.dwEffects & CFE_LINK;
return (text_format.dwEffects & CFE_LINK) ? 1 : 0;
}
static void check_CFE_LINK_rcvd(HWND hwnd, int is_url, const char * url)
{
int link_present = 0;
link_present = check_CFE_LINK_selection(hwnd, 0, 1);
if (is_url)
{ /* control text is url; should get CFE_LINK */
ok(0 != link_present, "URL Case: CFE_LINK not set for [%s].\n", url);
@ -971,10 +977,70 @@ static void test_EM_AUTOURLDETECT(void)
{"wais:waisserver", 1}
};
int i;
int i, j;
int urlRet=-1;
HWND hwndRichEdit, parent;
/* All of the following should cause the URL to be detected */
const char * templates_delim[] = {
"This is some text with X on it",
"This is some text with (X) on it",
"This is some text with X\r on it",
"This is some text with ---X--- on it",
"This is some text with \"X\" on it",
"This is some text with 'X' on it",
"This is some text with 'X' on it",
"This is some text with :X: on it",
"This text ends with X",
"This is some text with X) on it",
"This is some text with X--- on it",
"This is some text with X\" on it",
"This is some text with X' on it",
"This is some text with X: on it",
"This is some text with (X on it",
"This is some text with \rX on it",
"This is some text with ---X on it",
"This is some text with \"X on it",
"This is some text with 'X on it",
"This is some text with :X on it",
};
/* None of these should cause the URL to be detected */
const char * templates_non_delim[] = {
"This is some text with |X| on it",
"This is some text with *X* on it",
"This is some text with /X/ on it",
"This is some text with +X+ on it",
"This is some text with %X% on it",
"This is some text with #X# on it",
"This is some text with @X@ on it",
"This is some text with \\X\\ on it",
"This is some text with |X on it",
"This is some text with *X on it",
"This is some text with /X on it",
"This is some text with +X on it",
"This is some text with %X on it",
"This is some text with #X on it",
"This is some text with @X on it",
"This is some text with \\X on it",
};
/* All of these cause the URL detection to be extended by one more byte,
thus demonstrating that the tested character is considered as part
of the URL. */
const char * templates_xten_delim[] = {
"This is some text with X| on it",
"This is some text with X* on it",
"This is some text with X/ on it",
"This is some text with X+ on it",
"This is some text with X% on it",
"This is some text with X# on it",
"This is some text with X@ on it",
"This is some text with X\\ on it",
};
char buffer[1024];
parent = new_static_wnd(NULL);
hwndRichEdit = new_richedit(parent);
/* Try and pass EM_AUTOURLDETECT some test wParam values */
@ -989,16 +1055,173 @@ static void test_EM_AUTOURLDETECT(void)
ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
/* for each url, check the text to see if CFE_LINK effect is present */
for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
check_CFE_LINK_rcvd(hwndRichEdit, 0, urls[i].text);
/* Link detection should happen immediately upon WM_SETTEXT */
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url, urls[i].text);
}
DestroyWindow(hwndRichEdit);
/* Test detection of URLs within normal text - WM_SETTEXT case. */
for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
hwndRichEdit = new_richedit(parent);
for (j = 0; j < sizeof(templates_delim) / sizeof(const char *); j++) {
char * at_pos;
int at_offset;
int end_offset;
at_pos = strchr(templates_delim[j], 'X');
at_offset = at_pos - templates_delim[j];
strncpy(buffer, templates_delim[j], at_offset);
buffer[at_offset] = '\0';
strcat(buffer, urls[i].text);
strcat(buffer, templates_delim[j] + at_offset + 1);
end_offset = at_offset + strlen(urls[i].text);
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
/* This assumes no templates start with the URL itself, and that they
have at least two characters before the URL text */
ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
if (urls[i].is_url)
{
ok(check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
"CFE_LINK not set in (%d-%d), text: %s\n", at_offset, at_offset +1, buffer);
ok(check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
"CFE_LINK not set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
}
else
{
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
}
if (buffer[end_offset] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset + 1, buffer);
if (buffer[end_offset +1] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset +2, buffer);
}
}
}
for (j = 0; j < sizeof(templates_non_delim) / sizeof(const char *); j++) {
char * at_pos;
int at_offset;
int end_offset;
at_pos = strchr(templates_non_delim[j], 'X');
at_offset = at_pos - templates_non_delim[j];
strncpy(buffer, templates_non_delim[j], at_offset);
buffer[at_offset] = '\0';
strcat(buffer, urls[i].text);
strcat(buffer, templates_non_delim[j] + at_offset + 1);
end_offset = at_offset + strlen(urls[i].text);
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
/* This assumes no templates start with the URL itself, and that they
have at least two characters before the URL text */
ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
if (buffer[end_offset] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset + 1, buffer);
if (buffer[end_offset +1] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset +2, buffer);
}
}
}
for (j = 0; j < sizeof(templates_xten_delim) / sizeof(const char *); j++) {
char * at_pos;
int at_offset;
int end_offset;
at_pos = strchr(templates_xten_delim[j], 'X');
at_offset = at_pos - templates_xten_delim[j];
strncpy(buffer, templates_xten_delim[j], at_offset);
buffer[at_offset] = '\0';
strcat(buffer, urls[i].text);
strcat(buffer, templates_xten_delim[j] + at_offset + 1);
end_offset = at_offset + strlen(urls[i].text);
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
/* This assumes no templates start with the URL itself, and that they
have at least two characters before the URL text */
ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
if (urls[i].is_url)
{
ok(check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
"CFE_LINK not set in (%d-%d), text: %s\n", at_offset, at_offset +1, buffer);
ok(check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
"CFE_LINK not set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
ok(check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
"CFE_LINK not set in (%d-%d), text: %s\n", end_offset, end_offset +1, buffer);
}
else
{
ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset +1, buffer);
}
if (buffer[end_offset +1] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset + 2, buffer);
if (buffer[end_offset +2] != '\0')
{
ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +2, end_offset +3),
"CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +2, end_offset +3, buffer);
}
}
}
DestroyWindow(hwndRichEdit);
hwndRichEdit = NULL;
}
DestroyWindow(parent);
}
@ -3259,7 +3482,6 @@ START_TEST( editor )
test_WM_GETTEXT();
test_EM_GETTEXTRANGE();
test_EM_GETSELTEXT();
test_EM_AUTOURLDETECT();
test_EM_SETUNDOLIMIT();
test_ES_PASSWORD();
test_EM_SETTEXTEX();
@ -3270,6 +3492,7 @@ START_TEST( editor )
test_EM_GETMODIFY();
test_EM_EXSETSEL();
test_WM_PASTE();
test_EM_AUTOURLDETECT();
test_EM_STREAMIN();
test_EM_STREAMOUT();
test_EM_StreamIn_Undo();