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