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