mshtml: Added IDispatchEx support to HTMLInputElement.
[wine] / dlls / mshtml / txtrange.c
1 /*
2  * Copyright 2006-2007 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20
21 #define COBJMACROS
22
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "ole2.h"
27 #include "mshtmcid.h"
28
29 #include "wine/debug.h"
30 #include "wine/unicode.h"
31
32 #include "mshtml_private.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
35
36 static const WCHAR brW[] = {'b','r',0};
37 static const WCHAR hrW[] = {'h','r',0};
38
39 typedef struct {
40     const IHTMLTxtRangeVtbl *lpHTMLTxtRangeVtbl;
41     const IOleCommandTargetVtbl *lpOleCommandTargetVtbl;
42
43     LONG ref;
44
45     nsIDOMRange *nsrange;
46     HTMLDocument *doc;
47
48     struct list entry;
49 } HTMLTxtRange;
50
51 #define HTMLTXTRANGE(x)  ((IHTMLTxtRange*)  &(x)->lpHTMLTxtRangeVtbl)
52
53 typedef struct {
54     WCHAR *buf;
55     DWORD len;
56     DWORD size;
57 } wstrbuf_t;
58
59 typedef struct {
60     PRUint16 type;
61     nsIDOMNode *node;
62     PRUint32 off;
63     nsAString str;
64     const PRUnichar *p;
65 } dompos_t;
66
67 typedef enum {
68     RU_UNKNOWN,
69     RU_CHAR,
70     RU_WORD,
71     RU_SENTENCE,
72     RU_TEXTEDIT
73 } range_unit_t;
74
75 static HTMLTxtRange *get_range_object(HTMLDocument *doc, IHTMLTxtRange *iface)
76 {
77     HTMLTxtRange *iter;
78
79     LIST_FOR_EACH_ENTRY(iter, &doc->range_list, HTMLTxtRange, entry) {
80         if(HTMLTXTRANGE(iter) == iface)
81             return iter;
82     }
83
84     ERR("Could not find range in document\n");
85     return NULL;
86 }
87
88 static range_unit_t string_to_unit(LPCWSTR str)
89 {
90     static const WCHAR characterW[] =
91         {'c','h','a','r','a','c','t','e','r',0};
92     static const WCHAR wordW[] =
93         {'w','o','r','d',0};
94     static const WCHAR sentenceW[] =
95         {'s','e','n','t','e','n','c','e',0};
96     static const WCHAR texteditW[] =
97         {'t','e','x','t','e','d','i','t',0};
98
99     if(!strcmpiW(str, characterW))  return RU_CHAR;
100     if(!strcmpiW(str, wordW))       return RU_WORD;
101     if(!strcmpiW(str, sentenceW))   return RU_SENTENCE;
102     if(!strcmpiW(str, texteditW))   return RU_TEXTEDIT;
103
104     return RU_UNKNOWN;
105 }
106
107 static int string_to_nscmptype(LPCWSTR str)
108 {
109     static const WCHAR seW[] = {'S','t','a','r','t','T','o','E','n','d',0};
110     static const WCHAR ssW[] = {'S','t','a','r','t','T','o','S','t','a','r','t',0};
111     static const WCHAR esW[] = {'E','n','d','T','o','S','t','a','r','t',0};
112     static const WCHAR eeW[] = {'E','n','d','T','o','E','n','d',0};
113
114     if(!strcmpiW(str, seW))  return NS_START_TO_END;
115     if(!strcmpiW(str, ssW))  return NS_START_TO_START;
116     if(!strcmpiW(str, esW))  return NS_END_TO_START;
117     if(!strcmpiW(str, eeW))  return NS_END_TO_END;
118
119     return -1;
120 }
121
122 static PRUint16 get_node_type(nsIDOMNode *node)
123 {
124     PRUint16 type = 0;
125
126     if(node)
127         nsIDOMNode_GetNodeType(node, &type);
128
129     return type;
130 }
131
132 static BOOL is_elem_tag(nsIDOMNode *node, LPCWSTR istag)
133 {
134     nsIDOMElement *elem;
135     nsAString tag_str;
136     const PRUnichar *tag;
137     BOOL ret = FALSE;
138     nsresult nsres;
139
140     nsres = nsIDOMNode_QueryInterface(node, &IID_nsIDOMElement, (void**)&elem);
141     if(NS_FAILED(nsres))
142         return FALSE;
143
144     nsAString_Init(&tag_str, NULL);
145     nsIDOMElement_GetTagName(elem, &tag_str);
146     nsIDOMElement_Release(elem);
147     nsAString_GetData(&tag_str, &tag);
148
149     ret = !strcmpiW(tag, istag);
150
151     nsAString_Finish(&tag_str);
152
153     return ret;
154 }
155
156 static BOOL is_space_elem(nsIDOMNode *node)
157 {
158     nsIDOMElement *elem;
159     nsAString tag_str;
160     const PRUnichar *tag;
161     BOOL ret = FALSE;
162     nsresult nsres;
163
164     nsres = nsIDOMNode_QueryInterface(node, &IID_nsIDOMElement, (void**)&elem);
165     if(NS_FAILED(nsres))
166         return FALSE;
167
168     nsAString_Init(&tag_str, NULL);
169     nsIDOMElement_GetTagName(elem, &tag_str);
170     nsIDOMElement_Release(elem);
171     nsAString_GetData(&tag_str, &tag);
172
173     ret = !strcmpiW(tag, brW) || !strcmpiW(tag, hrW);
174
175     nsAString_Finish(&tag_str);
176
177     return ret;
178 }
179
180 static inline void wstrbuf_init(wstrbuf_t *buf)
181 {
182     buf->len = 0;
183     buf->size = 16;
184     buf->buf = heap_alloc(buf->size * sizeof(WCHAR));
185     *buf->buf = 0;
186 }
187
188 static inline void wstrbuf_finish(wstrbuf_t *buf)
189 {
190     heap_free(buf->buf);
191 }
192
193 static void wstrbuf_append_len(wstrbuf_t *buf, LPCWSTR str, int len)
194 {
195     if(buf->len+len >= buf->size) {
196         buf->size = 2*buf->len+len;
197         buf->buf = heap_realloc(buf->buf, buf->size * sizeof(WCHAR));
198     }
199
200     memcpy(buf->buf+buf->len, str, len*sizeof(WCHAR));
201     buf->len += len;
202     buf->buf[buf->len] = 0;
203 }
204
205 static inline void wstrbuf_append(wstrbuf_t *buf, LPCWSTR str)
206 {
207     wstrbuf_append_len(buf, str, strlenW(str));
208 }
209
210 static void wstrbuf_append_nodetxt(wstrbuf_t *buf, LPCWSTR str, int len)
211 {
212     const WCHAR *s = str;
213     WCHAR *d;
214
215     TRACE("%s\n", debugstr_wn(str, len));
216
217     if(buf->len+len >= buf->size) {
218         buf->size = 2*buf->len+len;
219         buf->buf = heap_realloc(buf->buf, buf->size * sizeof(WCHAR));
220     }
221
222     if(buf->len && isspaceW(buf->buf[buf->len-1])) {
223         while(s < str+len && isspaceW(*s))
224             s++;
225     }
226
227     d = buf->buf+buf->len;
228     while(s < str+len) {
229         if(isspaceW(*s)) {
230             *d++ = ' ';
231             s++;
232             while(s < str+len && isspaceW(*s))
233                 s++;
234         }else {
235             *d++ = *s++;
236         }
237     }
238
239     buf->len = d - buf->buf;
240     *d = 0;
241 }
242
243 static void wstrbuf_append_node(wstrbuf_t *buf, nsIDOMNode *node)
244 {
245
246     switch(get_node_type(node)) {
247     case TEXT_NODE: {
248         nsIDOMText *nstext;
249         nsAString data_str;
250         const PRUnichar *data;
251
252         nsIDOMNode_QueryInterface(node, &IID_nsIDOMText, (void**)&nstext);
253
254         nsAString_Init(&data_str, NULL);
255         nsIDOMText_GetData(nstext, &data_str);
256         nsAString_GetData(&data_str, &data);
257         wstrbuf_append_nodetxt(buf, data, strlenW(data));
258         nsAString_Finish(&data_str);
259
260        nsIDOMText_Release(nstext);
261
262         break;
263     }
264     case ELEMENT_NODE:
265         if(is_elem_tag(node, brW)) {
266             static const WCHAR endlW[] = {'\r','\n'};
267             wstrbuf_append_len(buf, endlW, 2);
268         }else if(is_elem_tag(node, hrW)) {
269             static const WCHAR endl2W[] = {'\r','\n','\r','\n'};
270             wstrbuf_append_len(buf, endl2W, 4);
271         }
272     }
273 }
274
275 static BOOL fill_nodestr(dompos_t *pos)
276 {
277     nsIDOMText *text;
278     nsresult nsres;
279
280     if(pos->type != TEXT_NODE)
281         return FALSE;
282
283     nsres = nsIDOMNode_QueryInterface(pos->node, &IID_nsIDOMText, (void**)&text);
284     if(NS_FAILED(nsres))
285         return FALSE;
286
287     nsAString_Init(&pos->str, NULL);
288     nsIDOMText_GetData(text, &pos->str);
289     nsIDOMText_Release(text);
290     nsAString_GetData(&pos->str, &pos->p);
291
292     if(pos->off == -1)
293         pos->off = *pos->p ? strlenW(pos->p)-1 : 0;
294
295     return TRUE;
296 }
297
298 static nsIDOMNode *next_node(nsIDOMNode *iter)
299 {
300     nsIDOMNode *ret, *tmp;
301     nsresult nsres;
302
303     if(!iter)
304         return NULL;
305
306     nsres = nsIDOMNode_GetFirstChild(iter, &ret);
307     if(NS_SUCCEEDED(nsres) && ret)
308         return ret;
309
310     nsIDOMNode_AddRef(iter);
311
312     do {
313         nsres = nsIDOMNode_GetNextSibling(iter, &ret);
314         if(NS_SUCCEEDED(nsres) && ret) {
315             nsIDOMNode_Release(iter);
316             return ret;
317         }
318
319         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
320         nsIDOMNode_Release(iter);
321         iter = tmp;
322     }while(NS_SUCCEEDED(nsres) && iter);
323
324     return NULL;
325 }
326
327 static nsIDOMNode *prev_node(HTMLTxtRange *This, nsIDOMNode *iter)
328 {
329     nsIDOMNode *ret, *tmp;
330     nsresult nsres;
331
332     if(!iter) {
333         nsIDOMHTMLDocument *nshtmldoc;
334         nsIDOMHTMLElement *nselem;
335         nsIDOMDocument *nsdoc;
336
337         nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
338         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
339         nsIDOMDocument_Release(nsdoc);
340         nsIDOMHTMLDocument_GetBody(nshtmldoc, &nselem);
341         nsIDOMHTMLDocument_Release(nshtmldoc);
342
343         nsIDOMElement_GetLastChild(nselem, &tmp);
344         if(!tmp)
345             return (nsIDOMNode*)nselem;
346
347         while(tmp) {
348             ret = tmp;
349             nsIDOMNode_GetLastChild(ret, &tmp);
350         }
351
352         nsIDOMElement_Release(nselem);
353
354         return ret;
355     }
356
357     nsres = nsIDOMNode_GetLastChild(iter, &ret);
358     if(NS_SUCCEEDED(nsres) && ret)
359         return ret;
360
361     nsIDOMNode_AddRef(iter);
362
363     do {
364         nsres = nsIDOMNode_GetPreviousSibling(iter, &ret);
365         if(NS_SUCCEEDED(nsres) && ret) {
366             nsIDOMNode_Release(iter);
367             return ret;
368         }
369
370         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
371         nsIDOMNode_Release(iter);
372         iter = tmp;
373     }while(NS_SUCCEEDED(nsres) && iter);
374
375     return NULL;
376 }
377
378 static nsIDOMNode *get_child_node(nsIDOMNode *node, PRUint32 off)
379 {
380     nsIDOMNodeList *node_list;
381     nsIDOMNode *ret = NULL;
382
383     nsIDOMNode_GetChildNodes(node, &node_list);
384     nsIDOMNodeList_Item(node_list, off, &ret);
385     nsIDOMNodeList_Release(node_list);
386
387     return ret;
388 }
389
390 static void get_cur_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
391 {
392     nsIDOMNode *node;
393     PRInt32 off;
394
395     pos->p = NULL;
396
397     if(!start) {
398         PRBool collapsed;
399         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
400         start = collapsed;
401     }
402
403     if(start) {
404         nsIDOMRange_GetStartContainer(This->nsrange, &node);
405         nsIDOMRange_GetStartOffset(This->nsrange, &off);
406     }else {
407         nsIDOMRange_GetEndContainer(This->nsrange, &node);
408         nsIDOMRange_GetEndOffset(This->nsrange, &off);
409     }
410
411     pos->type = get_node_type(node);
412     if(pos->type == ELEMENT_NODE) {
413         if(start) {
414             pos->node = get_child_node(node, off);
415             pos->off = 0;
416         }else {
417             pos->node = off ? get_child_node(node, off-1) : prev_node(This, node);
418             pos->off = -1;
419         }
420
421         pos->type = get_node_type(pos->node);
422         nsIDOMNode_Release(node);
423     }else if(start) {
424         pos->node = node;
425         pos->off = off;
426     }else if(off) {
427         pos->node = node;
428         pos->off = off-1;
429     }else {
430         pos->node = prev_node(This, node);
431         pos->off = -1;
432         nsIDOMNode_Release(node);
433     }
434
435     if(pos->type == TEXT_NODE)
436         fill_nodestr(pos);
437 }
438
439 static void set_range_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
440 {
441     nsresult nsres;
442
443     if(start) {
444         if(pos->type == TEXT_NODE)
445             nsres = nsIDOMRange_SetStart(This->nsrange, pos->node, pos->off);
446         else
447             nsres = nsIDOMRange_SetStartBefore(This->nsrange, pos->node);
448     }else {
449         if(pos->type == TEXT_NODE && pos->p[pos->off+1])
450             nsres = nsIDOMRange_SetEnd(This->nsrange, pos->node, pos->off+1);
451         else
452             nsres = nsIDOMRange_SetEndAfter(This->nsrange, pos->node);
453     }
454
455     if(NS_FAILED(nsres))
456         ERR("failed: %p %08x\n", pos->node, nsres);
457 }
458
459 static inline void dompos_release(dompos_t *pos)
460 {
461     if(pos->node)
462         nsIDOMNode_Release(pos->node);
463
464     if(pos->p)
465         nsAString_Finish(&pos->str);
466 }
467
468 static inline void dompos_addref(dompos_t *pos)
469 {
470     if(pos->node)
471         nsIDOMNode_AddRef(pos->node);
472
473     if(pos->type == TEXT_NODE)
474         fill_nodestr(pos);
475 }
476
477 static inline BOOL dompos_cmp(const dompos_t *pos1, const dompos_t *pos2)
478 {
479     return pos1->node == pos2->node && pos1->off == pos2->off;
480 }
481
482 static void range_to_string(HTMLTxtRange *This, wstrbuf_t *buf)
483 {
484     nsIDOMNode *iter, *tmp;
485     dompos_t start_pos, end_pos;
486     PRBool collapsed;
487
488     nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
489     if(collapsed) {
490         wstrbuf_finish(buf);
491         buf->buf = NULL;
492         buf->size = 0;
493         return;
494     }
495
496     get_cur_pos(This, FALSE, &end_pos);
497     get_cur_pos(This, TRUE, &start_pos);
498
499     if(start_pos.type == TEXT_NODE) {
500         if(start_pos.node == end_pos.node) {
501             wstrbuf_append_nodetxt(buf, start_pos.p+start_pos.off, end_pos.off-start_pos.off+1);
502             iter = start_pos.node;
503             nsIDOMNode_AddRef(iter);
504         }else {
505             wstrbuf_append_nodetxt(buf, start_pos.p+start_pos.off, strlenW(start_pos.p+start_pos.off));
506             iter = next_node(start_pos.node);
507         }
508     }else {
509         iter = start_pos.node;
510         nsIDOMNode_AddRef(iter);
511     }
512
513     while(iter != end_pos.node) {
514         wstrbuf_append_node(buf, iter);
515         tmp = next_node(iter);
516         nsIDOMNode_Release(iter);
517         iter = tmp;
518     }
519
520     nsIDOMNode_AddRef(end_pos.node);
521
522     if(start_pos.node != end_pos.node) {
523         if(end_pos.type == TEXT_NODE)
524             wstrbuf_append_nodetxt(buf, end_pos.p, end_pos.off+1);
525         else
526             wstrbuf_append_node(buf, end_pos.node);
527     }
528
529     nsIDOMNode_Release(iter);
530     dompos_release(&start_pos);
531     dompos_release(&end_pos);
532
533     if(buf->len) {
534         WCHAR *p;
535
536         for(p = buf->buf+buf->len-1; p >= buf->buf && isspaceW(*p); p--);
537
538         p = strchrW(p, '\r');
539         if(p)
540             *p = 0;
541     }
542 }
543
544 static WCHAR get_pos_char(const dompos_t *pos)
545 {
546     switch(pos->type) {
547     case TEXT_NODE:
548         return pos->p[pos->off];
549     case ELEMENT_NODE:
550         if(is_space_elem(pos->node))
551             return '\n';
552     }
553
554     return 0;
555 }
556
557 static void end_space(const dompos_t *pos, dompos_t *new_pos)
558 {
559     const WCHAR *p;
560
561     *new_pos = *pos;
562     dompos_addref(new_pos);
563
564     if(pos->type != TEXT_NODE)
565         return;
566
567     p = new_pos->p+new_pos->off;
568
569     if(!*p || !isspace(*p))
570         return;
571
572     while(p[1] && isspace(p[1]))
573         p++;
574
575     new_pos->off = p - new_pos->p;
576 }
577
578 static WCHAR next_char(const dompos_t *pos, dompos_t *new_pos)
579 {
580     nsIDOMNode *iter, *tmp;
581     dompos_t last_space, tmp_pos;
582     const WCHAR *p;
583     WCHAR cspace = 0;
584
585     if(pos->type == TEXT_NODE && pos->off != -1 && pos->p[pos->off]) {
586         p = pos->p+pos->off;
587
588         if(isspace(*p))
589             while(isspaceW(*++p));
590         else
591             p++;
592
593         if(*p && isspaceW(*p)) {
594             cspace = ' ';
595             while(p[1] && isspaceW(p[1]))
596                 p++;
597         }
598
599         if(*p) {
600             *new_pos = *pos;
601             new_pos->off = p - pos->p;
602             dompos_addref(new_pos);
603
604             return cspace ? cspace : *p;
605         }else {
606             last_space = *pos;
607             last_space.off = p - pos->p;
608             dompos_addref(&last_space);
609         }
610     }
611
612     iter = next_node(pos->node);
613
614     while(iter) {
615         switch(get_node_type(iter)) {
616         case TEXT_NODE:
617             tmp_pos.node = iter;
618             tmp_pos.type = TEXT_NODE;
619             dompos_addref(&tmp_pos);
620
621             p = tmp_pos.p;
622
623             if(!*p) {
624                 dompos_release(&tmp_pos);
625                 break;
626             }else if(isspaceW(*p)) {
627                 if(cspace)
628                     dompos_release(&last_space);
629                 else
630                     cspace = ' ';
631
632                 while(p[1] && isspaceW(p[1]))
633                       p++;
634
635                 tmp_pos.off = p-tmp_pos.p;
636
637                 if(!p[1]) {
638                     last_space = tmp_pos;
639                     break;
640                 }
641
642                 *new_pos = tmp_pos;
643                 nsIDOMNode_Release(iter);
644                 return cspace;
645             }else if(cspace) {
646                 *new_pos = last_space;
647                 dompos_release(&tmp_pos);
648                 nsIDOMNode_Release(iter);
649
650                 return cspace;
651             }else if(*p) {
652                 tmp_pos.off = 0;
653                 *new_pos = tmp_pos;
654             }
655
656             nsIDOMNode_Release(iter);
657             return *p;
658
659         case ELEMENT_NODE:
660             if(is_elem_tag(iter, brW)) {
661                 if(cspace)
662                     dompos_release(&last_space);
663                 cspace = '\n';
664
665                 nsIDOMNode_AddRef(iter);
666                 last_space.node = iter;
667                 last_space.type = ELEMENT_NODE;
668                 last_space.off = 0;
669                 last_space.p = NULL;
670             }else if(is_elem_tag(iter, hrW)) {
671                 if(cspace) {
672                     *new_pos = last_space;
673                     nsIDOMNode_Release(iter);
674                     return cspace;
675                 }
676
677                 new_pos->node = iter;
678                 new_pos->type = ELEMENT_NODE;
679                 new_pos->off = 0;
680                 new_pos->p = NULL;
681                 return '\n';
682             }
683         }
684
685         tmp = iter;
686         iter = next_node(iter);
687         nsIDOMNode_Release(tmp);
688     }
689
690     if(cspace) {
691         *new_pos = last_space;
692     }else {
693         *new_pos = *pos;
694         dompos_addref(new_pos);
695     }
696
697     return cspace;
698 }
699
700 static WCHAR prev_char(HTMLTxtRange *This, const dompos_t *pos, dompos_t *new_pos)
701 {
702     nsIDOMNode *iter, *tmp;
703     const WCHAR *p;
704     BOOL skip_space = FALSE;
705
706     if(pos->type == TEXT_NODE && isspaceW(pos->p[pos->off]))
707         skip_space = TRUE;
708
709     if(pos->type == TEXT_NODE && pos->off) {
710         p = pos->p+pos->off-1;
711
712         if(skip_space) {
713             while(p >= pos->p && isspace(*p))
714                 p--;
715         }
716
717         if(p >= pos->p) {
718             *new_pos = *pos;
719             new_pos->off = p-pos->p;
720             dompos_addref(new_pos);
721             return new_pos->p[new_pos->off];
722         }
723     }
724
725     iter = prev_node(This, pos->node);
726
727     while(iter) {
728         switch(get_node_type(iter)) {
729         case TEXT_NODE: {
730             dompos_t tmp_pos;
731
732             tmp_pos.node = iter;
733             tmp_pos.type = TEXT_NODE;
734             dompos_addref(&tmp_pos);
735
736             p = tmp_pos.p + strlenW(tmp_pos.p)-1;
737
738             if(skip_space) {
739                 while(p >= tmp_pos.p && isspaceW(*p))
740                     p--;
741             }
742
743             if(p < tmp_pos.p) {
744                 dompos_release(&tmp_pos);
745                 break;
746             }
747
748             tmp_pos.off = p-tmp_pos.p;
749             *new_pos = tmp_pos;
750             nsIDOMNode_Release(iter);
751             return *p;
752         }
753
754         case ELEMENT_NODE:
755             if(is_elem_tag(iter, brW)) {
756                 if(skip_space) {
757                     skip_space = FALSE;
758                     break;
759                 }
760             }else if(!is_elem_tag(iter, hrW)) {
761                 break;
762             }
763
764             new_pos->node = iter;
765             new_pos->type = ELEMENT_NODE;
766             new_pos->off = 0;
767             new_pos->p = NULL;
768             return '\n';
769         }
770
771         tmp = iter;
772         iter = prev_node(This, iter);
773         nsIDOMNode_Release(tmp);
774     }
775
776     *new_pos = *pos;
777     dompos_addref(new_pos);
778     return 0;
779 }
780
781 static long move_next_chars(long cnt, const dompos_t *pos, BOOL col, const dompos_t *bound_pos,
782         BOOL *bounded, dompos_t *new_pos)
783 {
784     dompos_t iter, tmp;
785     long ret = 0;
786     WCHAR c;
787
788     if(bounded)
789         *bounded = FALSE;
790
791     if(col)
792         ret++;
793
794     if(ret >= cnt) {
795         end_space(pos, new_pos);
796         return ret;
797     }
798
799     c = next_char(pos, &iter);
800     ret++;
801
802     while(ret < cnt) {
803         tmp = iter;
804         c = next_char(&tmp, &iter);
805         dompos_release(&tmp);
806         if(!c)
807             break;
808
809         ret++;
810         if(bound_pos && dompos_cmp(&tmp, bound_pos)) {
811             *bounded = TRUE;
812             ret++;
813         }
814     }
815
816     *new_pos = iter;
817     return ret;
818 }
819
820 static long move_prev_chars(HTMLTxtRange *This, long cnt, const dompos_t *pos, BOOL end,
821         const dompos_t *bound_pos, BOOL *bounded, dompos_t *new_pos)
822 {
823     dompos_t iter, tmp;
824     long ret = 0;
825     BOOL prev_eq = FALSE;
826     WCHAR c;
827
828     if(bounded)
829         *bounded = FALSE;
830
831     c = prev_char(This, pos, &iter);
832     if(c)
833         ret++;
834
835     while(c && ret < cnt) {
836         tmp = iter;
837         c = prev_char(This, &tmp, &iter);
838         dompos_release(&tmp);
839         if(!c) {
840             if(end)
841                 ret++;
842             break;
843         }
844
845         ret++;
846
847         if(prev_eq) {
848             *bounded = TRUE;
849             ret++;
850         }
851
852         prev_eq = bound_pos && dompos_cmp(&iter, bound_pos);
853     }
854
855     *new_pos = iter;
856     return ret;
857 }
858
859 static long find_prev_space(HTMLTxtRange *This, const dompos_t *pos, BOOL first_space, dompos_t *ret)
860 {
861     dompos_t iter, tmp;
862     WCHAR c;
863
864     c = prev_char(This, pos, &iter);
865     if(!c || (first_space && isspaceW(c))) {
866         *ret = iter;
867         return FALSE;
868     }
869
870     while(1) {
871         tmp = iter;
872         c = prev_char(This, &tmp, &iter);
873         if(!c || isspaceW(c)) {
874             dompos_release(&iter);
875             break;
876         }
877         dompos_release(&tmp);
878     }
879
880     *ret = tmp;
881     return TRUE;
882 }
883
884 static int find_word_end(const dompos_t *pos, dompos_t *ret)
885 {
886     dompos_t iter, tmp;
887     int cnt = 1;
888     WCHAR c;
889     c = get_pos_char(pos);
890     if(isspaceW(c)) {
891         *ret = *pos;
892         dompos_addref(ret);
893         return 0;
894     }
895
896     c = next_char(pos, &iter);
897     if(!c) {
898         *ret = iter;
899         return 0;
900     }
901     if(c == '\n') {
902         *ret = *pos;
903         dompos_addref(ret);
904         return 0;
905     }
906
907     while(c && !isspaceW(c)) {
908         tmp = iter;
909         c = next_char(&tmp, &iter);
910         if(c == '\n') {
911             dompos_release(&iter);
912             iter = tmp;
913         }else {
914             cnt++;
915             dompos_release(&tmp);
916         }
917     }
918
919     *ret = iter;
920     return cnt;
921 }
922
923 static long move_next_words(long cnt, const dompos_t *pos, dompos_t *new_pos)
924 {
925     dompos_t iter, tmp;
926     long ret = 0;
927     WCHAR c;
928
929     c = get_pos_char(pos);
930     if(isspaceW(c)) {
931         end_space(pos, &iter);
932         ret++;
933     }else {
934         c = next_char(pos, &iter);
935         if(c && isspaceW(c))
936             ret++;
937     }
938
939     while(c && ret < cnt) {
940         tmp = iter;
941         c = next_char(&tmp, &iter);
942         dompos_release(&tmp);
943         if(isspaceW(c))
944             ret++;
945     }
946
947     *new_pos = iter;
948     return ret;
949 }
950
951 static long move_prev_words(HTMLTxtRange *This, long cnt, const dompos_t *pos, dompos_t *new_pos)
952 {
953     dompos_t iter, tmp;
954     long ret = 0;
955
956     iter = *pos;
957     dompos_addref(&iter);
958
959     while(ret < cnt) {
960         if(!find_prev_space(This, &iter, FALSE, &tmp))
961             break;
962
963         dompos_release(&iter);
964         iter = tmp;
965         ret++;
966     }
967
968     *new_pos = iter;
969     return ret;
970 }
971
972 #define HTMLTXTRANGE_THIS(iface) DEFINE_THIS(HTMLTxtRange, HTMLTxtRange, iface)
973
974 static HRESULT WINAPI HTMLTxtRange_QueryInterface(IHTMLTxtRange *iface, REFIID riid, void **ppv)
975 {
976     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
977
978     *ppv = NULL;
979
980     if(IsEqualGUID(&IID_IUnknown, riid)) {
981         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
982         *ppv = HTMLTXTRANGE(This);
983     }else if(IsEqualGUID(&IID_IDispatch, riid)) {
984         TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
985         *ppv = HTMLTXTRANGE(This);
986     }else if(IsEqualGUID(&IID_IHTMLTxtRange, riid)) {
987         TRACE("(%p)->(IID_IHTMLTxtRange %p)\n", This, ppv);
988         *ppv = HTMLTXTRANGE(This);
989     }else if(IsEqualGUID(&IID_IOleCommandTarget, riid)) {
990         TRACE("(%p)->(IID_IOleCommandTarget %p)\n", This, ppv);
991         *ppv = CMDTARGET(This);
992     }
993
994     if(*ppv) {
995         IUnknown_AddRef((IUnknown*)*ppv);
996         return S_OK;
997     }
998
999     WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
1000     return E_NOINTERFACE;
1001 }
1002
1003 static ULONG WINAPI HTMLTxtRange_AddRef(IHTMLTxtRange *iface)
1004 {
1005     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1006     LONG ref = InterlockedIncrement(&This->ref);
1007
1008     TRACE("(%p) ref=%d\n", This, ref);
1009
1010     return ref;
1011 }
1012
1013 static ULONG WINAPI HTMLTxtRange_Release(IHTMLTxtRange *iface)
1014 {
1015     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1016     LONG ref = InterlockedDecrement(&This->ref);
1017
1018     TRACE("(%p) ref=%d\n", This, ref);
1019
1020     if(!ref) {
1021         if(This->nsrange)
1022             nsISelection_Release(This->nsrange);
1023         if(This->doc)
1024             list_remove(&This->entry);
1025         heap_free(This);
1026     }
1027
1028     return ref;
1029 }
1030
1031 static HRESULT WINAPI HTMLTxtRange_GetTypeInfoCount(IHTMLTxtRange *iface, UINT *pctinfo)
1032 {
1033     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1034     FIXME("(%p)->(%p)\n", This, pctinfo);
1035     return E_NOTIMPL;
1036 }
1037
1038 static HRESULT WINAPI HTMLTxtRange_GetTypeInfo(IHTMLTxtRange *iface, UINT iTInfo,
1039                                                LCID lcid, ITypeInfo **ppTInfo)
1040 {
1041     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1042     FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
1043     return E_NOTIMPL;
1044 }
1045
1046 static HRESULT WINAPI HTMLTxtRange_GetIDsOfNames(IHTMLTxtRange *iface, REFIID riid,
1047                                                  LPOLESTR *rgszNames, UINT cNames,
1048                                                  LCID lcid, DISPID *rgDispId)
1049 {
1050     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1051     FIXME("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
1052           lcid, rgDispId);
1053     return E_NOTIMPL;
1054 }
1055
1056 static HRESULT WINAPI HTMLTxtRange_Invoke(IHTMLTxtRange *iface, DISPID dispIdMember,
1057                             REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
1058                             VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
1059 {
1060     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1061     FIXME("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
1062           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
1063     return E_NOTIMPL;
1064 }
1065
1066 static HRESULT WINAPI HTMLTxtRange_get_htmlText(IHTMLTxtRange *iface, BSTR *p)
1067 {
1068     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1069
1070     TRACE("(%p)->(%p)\n", This, p);
1071
1072     *p = NULL;
1073
1074     if(This->nsrange) {
1075         nsIDOMDocumentFragment *fragment;
1076         nsresult nsres;
1077
1078         nsres = nsIDOMRange_CloneContents(This->nsrange, &fragment);
1079         if(NS_SUCCEEDED(nsres)) {
1080             const PRUnichar *nstext;
1081             nsAString nsstr;
1082
1083             nsAString_Init(&nsstr, NULL);
1084             nsnode_to_nsstring((nsIDOMNode*)fragment, &nsstr);
1085             nsIDOMDocumentFragment_Release(fragment);
1086
1087             nsAString_GetData(&nsstr, &nstext);
1088             *p = SysAllocString(nstext);
1089
1090             nsAString_Finish(&nsstr);
1091         }
1092     }
1093
1094     if(!*p) {
1095         const WCHAR emptyW[] = {0};
1096         *p = SysAllocString(emptyW);
1097     }
1098
1099     TRACE("return %s\n", debugstr_w(*p));
1100     return S_OK;
1101 }
1102
1103 static HRESULT WINAPI HTMLTxtRange_put_text(IHTMLTxtRange *iface, BSTR v)
1104 {
1105     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1106     nsIDOMDocument *nsdoc;
1107     nsIDOMText *text_node;
1108     nsAString text_str;
1109     nsresult nsres;
1110
1111     TRACE("(%p)->(%s)\n", This, debugstr_w(v));
1112
1113     if(!This->doc)
1114         return MSHTML_E_NODOC;
1115
1116     nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1117     if(NS_FAILED(nsres)) {
1118         ERR("GetDocument failed: %08x\n", nsres);
1119         return S_OK;
1120     }
1121
1122     nsAString_Init(&text_str, v);
1123     nsres = nsIDOMDocument_CreateTextNode(nsdoc, &text_str, &text_node);
1124     nsIDOMDocument_Release(nsdoc);
1125     nsAString_Finish(&text_str);
1126     if(NS_FAILED(nsres)) {
1127         ERR("CreateTextNode failed: %08x\n", nsres);
1128         return S_OK;
1129     }
1130     nsres = nsIDOMRange_DeleteContents(This->nsrange);
1131     if(NS_FAILED(nsres))
1132         ERR("DeleteContents failed: %08x\n", nsres);
1133
1134     nsres = nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)text_node);
1135     if(NS_FAILED(nsres))
1136         ERR("InsertNode failed: %08x\n", nsres);
1137
1138     nsres = nsIDOMRange_SetEndAfter(This->nsrange, (nsIDOMNode*)text_node);
1139     if(NS_FAILED(nsres))
1140         ERR("SetEndAfter failed: %08x\n", nsres);
1141
1142     return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), VARIANT_FALSE);
1143 }
1144
1145 static HRESULT WINAPI HTMLTxtRange_get_text(IHTMLTxtRange *iface, BSTR *p)
1146 {
1147     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1148     wstrbuf_t buf;
1149
1150     TRACE("(%p)->(%p)\n", This, p);
1151
1152     *p = NULL;
1153     if(!This->nsrange)
1154         return S_OK;
1155
1156     wstrbuf_init(&buf);
1157     range_to_string(This, &buf);
1158     if(buf.buf)
1159         *p = SysAllocString(buf.buf);
1160     wstrbuf_finish(&buf);
1161
1162     TRACE("ret %s\n", debugstr_w(*p));
1163     return S_OK;
1164 }
1165
1166 static HRESULT WINAPI HTMLTxtRange_parentElement(IHTMLTxtRange *iface, IHTMLElement **parent)
1167 {
1168     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1169     nsIDOMNode *nsnode, *tmp;
1170     HTMLDOMNode *node;
1171
1172     TRACE("(%p)->(%p)\n", This, parent);
1173
1174     nsIDOMRange_GetCommonAncestorContainer(This->nsrange, &nsnode);
1175     while(nsnode && get_node_type(nsnode) != ELEMENT_NODE) {
1176         nsIDOMNode_GetParentNode(nsnode, &tmp);
1177         nsIDOMNode_Release(nsnode);
1178         nsnode = tmp;
1179     }
1180
1181     if(!nsnode) {
1182         *parent = NULL;
1183         return S_OK;
1184     }
1185
1186     node = get_node(This->doc, nsnode, TRUE);
1187     nsIDOMNode_Release(nsnode);
1188
1189     return IHTMLDOMNode_QueryInterface(HTMLDOMNODE(node), &IID_IHTMLElement, (void**)parent);
1190 }
1191
1192 static HRESULT WINAPI HTMLTxtRange_duplicate(IHTMLTxtRange *iface, IHTMLTxtRange **Duplicate)
1193 {
1194     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1195     nsIDOMRange *nsrange = NULL;
1196
1197     TRACE("(%p)->(%p)\n", This, Duplicate);
1198
1199     nsIDOMRange_CloneRange(This->nsrange, &nsrange);
1200     *Duplicate = HTMLTxtRange_Create(This->doc, nsrange);
1201     nsIDOMRange_Release(nsrange);
1202
1203     return S_OK;
1204 }
1205
1206 static HRESULT WINAPI HTMLTxtRange_inRange(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1207         VARIANT_BOOL *InRange)
1208 {
1209     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1210     HTMLTxtRange *src_range;
1211     PRInt16 nsret = 0;
1212     nsresult nsres;
1213
1214     TRACE("(%p)->(%p %p)\n", This, Range, InRange);
1215
1216     *InRange = VARIANT_FALSE;
1217
1218     src_range = get_range_object(This->doc, Range);
1219     if(!src_range)
1220         return E_FAIL;
1221
1222     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1223             src_range->nsrange, &nsret);
1224     if(NS_SUCCEEDED(nsres) && nsret <= 0) {
1225         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1226                 src_range->nsrange, &nsret);
1227         if(NS_SUCCEEDED(nsres) && nsret >= 0)
1228             *InRange = VARIANT_TRUE;
1229     }
1230
1231     if(NS_FAILED(nsres))
1232         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1233
1234     return S_OK;
1235 }
1236
1237 static HRESULT WINAPI HTMLTxtRange_isEqual(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1238         VARIANT_BOOL *IsEqual)
1239 {
1240     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1241     HTMLTxtRange *src_range;
1242     PRInt16 nsret = 0;
1243     nsresult nsres;
1244
1245     TRACE("(%p)->(%p %p)\n", This, Range, IsEqual);
1246
1247     *IsEqual = VARIANT_FALSE;
1248
1249     src_range = get_range_object(This->doc, Range);
1250     if(!src_range)
1251         return E_FAIL;
1252
1253     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1254             src_range->nsrange, &nsret);
1255     if(NS_SUCCEEDED(nsres) && !nsret) {
1256         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1257                 src_range->nsrange, &nsret);
1258         if(NS_SUCCEEDED(nsres) && !nsret)
1259             *IsEqual = VARIANT_TRUE;
1260     }
1261
1262     if(NS_FAILED(nsres))
1263         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1264
1265     return S_OK;
1266 }
1267
1268 static HRESULT WINAPI HTMLTxtRange_scrollIntoView(IHTMLTxtRange *iface, VARIANT_BOOL fStart)
1269 {
1270     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1271     FIXME("(%p)->(%x)\n", This, fStart);
1272     return E_NOTIMPL;
1273 }
1274
1275 static HRESULT WINAPI HTMLTxtRange_collapse(IHTMLTxtRange *iface, VARIANT_BOOL Start)
1276 {
1277     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1278
1279     TRACE("(%p)->(%x)\n", This, Start);
1280
1281     nsIDOMRange_Collapse(This->nsrange, Start != VARIANT_FALSE);
1282     return S_OK;
1283 }
1284
1285 static HRESULT WINAPI HTMLTxtRange_expand(IHTMLTxtRange *iface, BSTR Unit, VARIANT_BOOL *Success)
1286 {
1287     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1288     range_unit_t unit;
1289
1290     TRACE("(%p)->(%s %p)\n", This, debugstr_w(Unit), Success);
1291
1292     unit = string_to_unit(Unit);
1293     if(unit == RU_UNKNOWN)
1294         return E_INVALIDARG;
1295
1296     *Success = VARIANT_FALSE;
1297
1298     switch(unit) {
1299     case RU_WORD: {
1300         dompos_t end_pos, start_pos, new_start_pos, new_end_pos;
1301         PRBool collapsed;
1302
1303         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1304
1305         get_cur_pos(This, TRUE, &start_pos);
1306         get_cur_pos(This, FALSE, &end_pos);
1307
1308         if(find_word_end(&end_pos, &new_end_pos) || collapsed) {
1309             set_range_pos(This, FALSE, &new_end_pos);
1310             *Success = VARIANT_TRUE;
1311         }
1312
1313         if(start_pos.type && (get_pos_char(&end_pos) || !dompos_cmp(&new_end_pos, &end_pos))) {
1314             if(find_prev_space(This, &start_pos, TRUE, &new_start_pos)) {
1315                 set_range_pos(This, TRUE, &new_start_pos);
1316                 *Success = VARIANT_TRUE;
1317             }
1318             dompos_release(&new_start_pos);
1319         }
1320
1321         dompos_release(&new_end_pos);
1322         dompos_release(&end_pos);
1323         dompos_release(&start_pos);
1324
1325         break;
1326     }
1327
1328     case RU_TEXTEDIT: {
1329         nsIDOMDocument *nsdoc;
1330         nsIDOMHTMLDocument *nshtmldoc;
1331         nsIDOMHTMLElement *nsbody = NULL;
1332         nsresult nsres;
1333
1334         nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1335         if(NS_FAILED(nsres) || !nsdoc) {
1336             ERR("GetDocument failed: %08x\n", nsres);
1337             break;
1338         }
1339
1340         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
1341         nsIDOMDocument_Release(nsdoc);
1342
1343         nsres = nsIDOMHTMLDocument_GetBody(nshtmldoc, &nsbody);
1344         nsIDOMHTMLDocument_Release(nshtmldoc);
1345         if(NS_FAILED(nsres) || !nsbody) {
1346             ERR("Could not get body: %08x\n", nsres);
1347             break;
1348         }
1349
1350         nsres = nsIDOMRange_SelectNodeContents(This->nsrange, (nsIDOMNode*)nsbody);
1351         nsIDOMHTMLElement_Release(nsbody);
1352         if(NS_FAILED(nsres)) {
1353             ERR("Collapse failed: %08x\n", nsres);
1354             break;
1355         }
1356
1357         *Success = VARIANT_TRUE;
1358         break;
1359     }
1360
1361     default:
1362         FIXME("Unimplemented unit %s\n", debugstr_w(Unit));
1363     }
1364
1365     return S_OK;
1366 }
1367
1368 static HRESULT WINAPI HTMLTxtRange_move(IHTMLTxtRange *iface, BSTR Unit,
1369         long Count, long *ActualCount)
1370 {
1371     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1372     range_unit_t unit;
1373
1374     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1375
1376     unit = string_to_unit(Unit);
1377     if(unit == RU_UNKNOWN)
1378         return E_INVALIDARG;
1379
1380     if(!Count) {
1381         *ActualCount = 0;
1382         return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1383     }
1384
1385     switch(unit) {
1386     case RU_CHAR: {
1387         dompos_t cur_pos, new_pos;
1388
1389         get_cur_pos(This, TRUE, &cur_pos);
1390
1391         if(Count > 0) {
1392             *ActualCount = move_next_chars(Count, &cur_pos, TRUE, NULL, NULL, &new_pos);
1393             set_range_pos(This, FALSE, &new_pos);
1394             dompos_release(&new_pos);
1395
1396             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1397         }else {
1398             *ActualCount = -move_prev_chars(This, -Count, &cur_pos, FALSE, NULL, NULL, &new_pos);
1399             set_range_pos(This, TRUE, &new_pos);
1400             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1401             dompos_release(&new_pos);
1402         }
1403
1404         dompos_release(&cur_pos);
1405         break;
1406     }
1407
1408     case RU_WORD: {
1409         dompos_t cur_pos, new_pos;
1410
1411         get_cur_pos(This, TRUE, &cur_pos);
1412
1413         if(Count > 0) {
1414             *ActualCount = move_next_words(Count, &cur_pos, &new_pos);
1415             set_range_pos(This, FALSE, &new_pos);
1416             dompos_release(&new_pos);
1417             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1418         }else {
1419             *ActualCount = -move_prev_words(This, -Count, &cur_pos, &new_pos);
1420             set_range_pos(This, TRUE, &new_pos);
1421             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1422             dompos_release(&new_pos);
1423         }
1424
1425         dompos_release(&cur_pos);
1426         break;
1427     }
1428
1429     default:
1430         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1431     }
1432
1433     TRACE("ret %ld\n", *ActualCount);
1434     return S_OK;
1435 }
1436
1437 static HRESULT WINAPI HTMLTxtRange_moveStart(IHTMLTxtRange *iface, BSTR Unit,
1438         long Count, long *ActualCount)
1439 {
1440     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1441     range_unit_t unit;
1442
1443     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1444
1445     unit = string_to_unit(Unit);
1446     if(unit == RU_UNKNOWN)
1447         return E_INVALIDARG;
1448
1449     if(!Count) {
1450         *ActualCount = 0;
1451         return S_OK;
1452     }
1453
1454     switch(unit) {
1455     case RU_CHAR: {
1456         dompos_t start_pos, end_pos, new_pos;
1457         PRBool collapsed;
1458
1459         get_cur_pos(This, TRUE, &start_pos);
1460         get_cur_pos(This, FALSE, &end_pos);
1461         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1462
1463         if(Count > 0) {
1464             BOOL bounded;
1465
1466             *ActualCount = move_next_chars(Count, &start_pos, collapsed, &end_pos, &bounded, &new_pos);
1467             set_range_pos(This, !bounded, &new_pos);
1468             if(bounded)
1469                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1470         }else {
1471             *ActualCount = -move_prev_chars(This, -Count, &start_pos, FALSE, NULL, NULL, &new_pos);
1472             set_range_pos(This, TRUE, &new_pos);
1473         }
1474
1475         dompos_release(&start_pos);
1476         dompos_release(&end_pos);
1477         dompos_release(&new_pos);
1478         break;
1479     }
1480
1481     default:
1482         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1483     }
1484
1485     return S_OK;
1486 }
1487
1488 static HRESULT WINAPI HTMLTxtRange_moveEnd(IHTMLTxtRange *iface, BSTR Unit,
1489         long Count, long *ActualCount)
1490 {
1491     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1492     range_unit_t unit;
1493
1494     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1495
1496     unit = string_to_unit(Unit);
1497     if(unit == RU_UNKNOWN)
1498         return E_INVALIDARG;
1499
1500     if(!Count) {
1501         *ActualCount = 0;
1502         return S_OK;
1503     }
1504
1505     switch(unit) {
1506     case RU_CHAR: {
1507         dompos_t start_pos, end_pos, new_pos;
1508         PRBool collapsed;
1509
1510         get_cur_pos(This, TRUE, &start_pos);
1511         get_cur_pos(This, FALSE, &end_pos);
1512         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1513
1514         if(Count > 0) {
1515             *ActualCount = move_next_chars(Count, &end_pos, collapsed, NULL, NULL, &new_pos);
1516             set_range_pos(This, FALSE, &new_pos);
1517         }else {
1518             BOOL bounded;
1519
1520             *ActualCount = -move_prev_chars(This, -Count, &end_pos, TRUE, &start_pos, &bounded, &new_pos);
1521             set_range_pos(This, bounded, &new_pos);
1522             if(bounded)
1523                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1524         }
1525
1526         dompos_release(&start_pos);
1527         dompos_release(&end_pos);
1528         dompos_release(&new_pos);
1529         break;
1530     }
1531
1532     default:
1533         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1534     }
1535
1536     return S_OK;
1537 }
1538
1539 static HRESULT WINAPI HTMLTxtRange_select(IHTMLTxtRange *iface)
1540 {
1541     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1542
1543     TRACE("(%p)\n", This);
1544
1545     if(This->doc->nscontainer) {
1546         nsIDOMWindow *dom_window = NULL;
1547         nsISelection *nsselection;
1548
1549         nsIWebBrowser_GetContentDOMWindow(This->doc->nscontainer->webbrowser, &dom_window);
1550         nsIDOMWindow_GetSelection(dom_window, &nsselection);
1551         nsIDOMWindow_Release(dom_window);
1552
1553         nsISelection_RemoveAllRanges(nsselection);
1554         nsISelection_AddRange(nsselection, This->nsrange);
1555
1556         nsISelection_Release(nsselection);
1557     }
1558
1559     return S_OK;
1560 }
1561
1562 static HRESULT WINAPI HTMLTxtRange_pasteHTML(IHTMLTxtRange *iface, BSTR html)
1563 {
1564     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1565     FIXME("(%p)->(%s)\n", This, debugstr_w(html));
1566     return E_NOTIMPL;
1567 }
1568
1569 static HRESULT WINAPI HTMLTxtRange_moveToElementText(IHTMLTxtRange *iface, IHTMLElement *element)
1570 {
1571     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1572     FIXME("(%p)->(%p)\n", This, element);
1573     return E_NOTIMPL;
1574 }
1575
1576 static HRESULT WINAPI HTMLTxtRange_setEndPoint(IHTMLTxtRange *iface, BSTR how,
1577         IHTMLTxtRange *SourceRange)
1578 {
1579     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1580     FIXME("(%p)->(%s %p)\n", This, debugstr_w(how), SourceRange);
1581     return E_NOTIMPL;
1582 }
1583
1584 static HRESULT WINAPI HTMLTxtRange_compareEndPoints(IHTMLTxtRange *iface, BSTR how,
1585         IHTMLTxtRange *SourceRange, long *ret)
1586 {
1587     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1588     HTMLTxtRange *src_range;
1589     PRInt16 nsret = 0;
1590     int nscmpt;
1591     nsresult nsres;
1592
1593     TRACE("(%p)->(%s %p %p)\n", This, debugstr_w(how), SourceRange, ret);
1594
1595     nscmpt = string_to_nscmptype(how);
1596     if(nscmpt == -1)
1597         return E_INVALIDARG;
1598
1599     src_range = get_range_object(This->doc, SourceRange);
1600     if(!src_range)
1601         return E_FAIL;
1602
1603     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, nscmpt, src_range->nsrange, &nsret);
1604     if(NS_FAILED(nsres))
1605         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1606
1607     *ret = nsret;
1608     return S_OK;
1609 }
1610
1611 static HRESULT WINAPI HTMLTxtRange_findText(IHTMLTxtRange *iface, BSTR String,
1612         long count, long Flags, VARIANT_BOOL *Success)
1613 {
1614     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1615     FIXME("(%p)->(%s %ld %08lx %p)\n", This, debugstr_w(String), count, Flags, Success);
1616     return E_NOTIMPL;
1617 }
1618
1619 static HRESULT WINAPI HTMLTxtRange_moveToPoint(IHTMLTxtRange *iface, long x, long y)
1620 {
1621     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1622     FIXME("(%p)->(%ld %ld)\n", This, x, y);
1623     return E_NOTIMPL;
1624 }
1625
1626 static HRESULT WINAPI HTMLTxtRange_getBookmark(IHTMLTxtRange *iface, BSTR *Bookmark)
1627 {
1628     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1629     FIXME("(%p)->(%p)\n", This, Bookmark);
1630     return E_NOTIMPL;
1631 }
1632
1633 static HRESULT WINAPI HTMLTxtRange_moveToBookmark(IHTMLTxtRange *iface, BSTR Bookmark,
1634         VARIANT_BOOL *Success)
1635 {
1636     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1637     FIXME("(%p)->(%s %p)\n", This, debugstr_w(Bookmark), Success);
1638     return E_NOTIMPL;
1639 }
1640
1641 static HRESULT WINAPI HTMLTxtRange_queryCommandSupported(IHTMLTxtRange *iface, BSTR cmdID,
1642         VARIANT_BOOL *pfRet)
1643 {
1644     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1645     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1646     return E_NOTIMPL;
1647 }
1648
1649 static HRESULT WINAPI HTMLTxtRange_queryCommandEnabled(IHTMLTxtRange *iface, BSTR cmdID,
1650         VARIANT_BOOL *pfRet)
1651 {
1652     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1653     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1654     return E_NOTIMPL;
1655 }
1656
1657 static HRESULT WINAPI HTMLTxtRange_queryCommandState(IHTMLTxtRange *iface, BSTR cmdID,
1658         VARIANT_BOOL *pfRet)
1659 {
1660     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1661     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1662     return E_NOTIMPL;
1663 }
1664
1665 static HRESULT WINAPI HTMLTxtRange_queryCommandIndeterm(IHTMLTxtRange *iface, BSTR cmdID,
1666         VARIANT_BOOL *pfRet)
1667 {
1668     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1669     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1670     return E_NOTIMPL;
1671 }
1672
1673 static HRESULT WINAPI HTMLTxtRange_queryCommandText(IHTMLTxtRange *iface, BSTR cmdID,
1674         BSTR *pcmdText)
1675 {
1676     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1677     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdText);
1678     return E_NOTIMPL;
1679 }
1680
1681 static HRESULT WINAPI HTMLTxtRange_queryCommandValue(IHTMLTxtRange *iface, BSTR cmdID,
1682         VARIANT *pcmdValue)
1683 {
1684     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1685     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdValue);
1686     return E_NOTIMPL;
1687 }
1688
1689 static HRESULT WINAPI HTMLTxtRange_execCommand(IHTMLTxtRange *iface, BSTR cmdID,
1690         VARIANT_BOOL showUI, VARIANT value, VARIANT_BOOL *pfRet)
1691 {
1692     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1693     FIXME("(%p)->(%s %x v %p)\n", This, debugstr_w(cmdID), showUI, pfRet);
1694     return E_NOTIMPL;
1695 }
1696
1697 static HRESULT WINAPI HTMLTxtRange_execCommandShowHelp(IHTMLTxtRange *iface, BSTR cmdID,
1698         VARIANT_BOOL *pfRet)
1699 {
1700     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1701     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1702     return E_NOTIMPL;
1703 }
1704
1705 #undef HTMLTXTRANGE_THIS
1706
1707 static const IHTMLTxtRangeVtbl HTMLTxtRangeVtbl = {
1708     HTMLTxtRange_QueryInterface,
1709     HTMLTxtRange_AddRef,
1710     HTMLTxtRange_Release,
1711     HTMLTxtRange_GetTypeInfoCount,
1712     HTMLTxtRange_GetTypeInfo,
1713     HTMLTxtRange_GetIDsOfNames,
1714     HTMLTxtRange_Invoke,
1715     HTMLTxtRange_get_htmlText,
1716     HTMLTxtRange_put_text,
1717     HTMLTxtRange_get_text,
1718     HTMLTxtRange_parentElement,
1719     HTMLTxtRange_duplicate,
1720     HTMLTxtRange_inRange,
1721     HTMLTxtRange_isEqual,
1722     HTMLTxtRange_scrollIntoView,
1723     HTMLTxtRange_collapse,
1724     HTMLTxtRange_expand,
1725     HTMLTxtRange_move,
1726     HTMLTxtRange_moveStart,
1727     HTMLTxtRange_moveEnd,
1728     HTMLTxtRange_select,
1729     HTMLTxtRange_pasteHTML,
1730     HTMLTxtRange_moveToElementText,
1731     HTMLTxtRange_setEndPoint,
1732     HTMLTxtRange_compareEndPoints,
1733     HTMLTxtRange_findText,
1734     HTMLTxtRange_moveToPoint,
1735     HTMLTxtRange_getBookmark,
1736     HTMLTxtRange_moveToBookmark,
1737     HTMLTxtRange_queryCommandSupported,
1738     HTMLTxtRange_queryCommandEnabled,
1739     HTMLTxtRange_queryCommandState,
1740     HTMLTxtRange_queryCommandIndeterm,
1741     HTMLTxtRange_queryCommandText,
1742     HTMLTxtRange_queryCommandValue,
1743     HTMLTxtRange_execCommand,
1744     HTMLTxtRange_execCommandShowHelp
1745 };
1746
1747 #define OLECMDTRG_THIS(iface) DEFINE_THIS(HTMLTxtRange, OleCommandTarget, iface)
1748
1749 static HRESULT WINAPI RangeCommandTarget_QueryInterface(IOleCommandTarget *iface, REFIID riid, void **ppv)
1750 {
1751     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1752     return IHTMLTxtRange_QueryInterface(HTMLTXTRANGE(This), riid, ppv);
1753 }
1754
1755 static ULONG WINAPI RangeCommandTarget_AddRef(IOleCommandTarget *iface)
1756 {
1757     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1758     return IHTMLTxtRange_AddRef(HTMLTXTRANGE(This));
1759 }
1760
1761 static ULONG WINAPI RangeCommandTarget_Release(IOleCommandTarget *iface)
1762 {
1763     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1764     return IHTMLTxtRange_Release(HTMLTXTRANGE(This));
1765 }
1766
1767 static HRESULT WINAPI RangeCommandTarget_QueryStatus(IOleCommandTarget *iface, const GUID *pguidCmdGroup,
1768         ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText)
1769 {
1770     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1771     FIXME("(%p)->(%s %d %p %p)\n", This, debugstr_guid(pguidCmdGroup), cCmds, prgCmds, pCmdText);
1772     return E_NOTIMPL;
1773 }
1774
1775 static HRESULT exec_indent(HTMLTxtRange *This, VARIANT *in, VARIANT *out)
1776 {
1777     nsIDOMDocumentFragment *fragment;
1778     nsIDOMElement *blockquote_elem, *p_elem;
1779     nsIDOMDocument *nsdoc;
1780     nsIDOMNode *tmp;
1781     nsAString tag_str;
1782
1783     static const PRUnichar blockquoteW[] = {'B','L','O','C','K','Q','U','O','T','E',0};
1784     static const PRUnichar pW[] = {'P',0};
1785
1786     TRACE("(%p)->(%p %p)\n", This, in, out);
1787
1788     nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1789
1790     nsAString_Init(&tag_str, blockquoteW);
1791     nsIDOMDocument_CreateElement(nsdoc, &tag_str, &blockquote_elem);
1792     nsAString_Finish(&tag_str);
1793
1794     nsAString_Init(&tag_str, pW);
1795     nsIDOMDocument_CreateElement(nsdoc, &tag_str, &p_elem);
1796     nsAString_Finish(&tag_str);
1797
1798     nsIDOMDocument_Release(nsdoc);
1799
1800     nsIDOMRange_ExtractContents(This->nsrange, &fragment);
1801     nsIDOMElement_AppendChild(p_elem, (nsIDOMNode*)fragment, &tmp);
1802     nsIDOMDocumentFragment_Release(fragment);
1803     nsIDOMNode_Release(tmp);
1804
1805     nsIDOMElement_AppendChild(blockquote_elem, (nsIDOMNode*)p_elem, &tmp);
1806     nsIDOMElement_Release(p_elem);
1807     nsIDOMNode_Release(tmp);
1808
1809     nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)blockquote_elem);
1810     nsIDOMElement_Release(blockquote_elem);
1811
1812     return S_OK;
1813 }
1814
1815 static HRESULT WINAPI RangeCommandTarget_Exec(IOleCommandTarget *iface, const GUID *pguidCmdGroup,
1816         DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
1817 {
1818     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1819
1820     TRACE("(%p)->(%s %d %x %p %p)\n", This, debugstr_guid(pguidCmdGroup), nCmdID,
1821           nCmdexecopt, pvaIn, pvaOut);
1822
1823     if(pguidCmdGroup && IsEqualGUID(&CGID_MSHTML, pguidCmdGroup)) {
1824         switch(nCmdID) {
1825         case IDM_INDENT:
1826             return exec_indent(This, pvaIn, pvaOut);
1827         default:
1828             FIXME("Unsupported cmdid %d of CGID_MSHTML\n", nCmdID);
1829         }
1830     }else {
1831         FIXME("Unsupported cmd %d of group %s\n", nCmdID, debugstr_guid(pguidCmdGroup));
1832     }
1833
1834     return E_NOTIMPL;
1835 }
1836
1837 #undef OLECMDTRG_THIS
1838
1839 static const IOleCommandTargetVtbl OleCommandTargetVtbl = {
1840     RangeCommandTarget_QueryInterface,
1841     RangeCommandTarget_AddRef,
1842     RangeCommandTarget_Release,
1843     RangeCommandTarget_QueryStatus,
1844     RangeCommandTarget_Exec
1845 };
1846
1847 IHTMLTxtRange *HTMLTxtRange_Create(HTMLDocument *doc, nsIDOMRange *nsrange)
1848 {
1849     HTMLTxtRange *ret = heap_alloc(sizeof(HTMLTxtRange));
1850
1851     ret->lpHTMLTxtRangeVtbl = &HTMLTxtRangeVtbl;
1852     ret->lpOleCommandTargetVtbl = &OleCommandTargetVtbl;
1853     ret->ref = 1;
1854
1855     if(nsrange)
1856         nsIDOMRange_AddRef(nsrange);
1857     ret->nsrange = nsrange;
1858
1859     ret->doc = doc;
1860     list_add_head(&doc->range_list, &ret->entry);
1861
1862     return HTMLTXTRANGE(ret);
1863 }
1864
1865 void detach_ranges(HTMLDocument *This)
1866 {
1867     HTMLTxtRange *iter;
1868
1869     LIST_FOR_EACH_ENTRY(iter, &This->range_list, HTMLTxtRange, entry) {
1870         iter->doc = NULL;
1871     }
1872 }