mshtml: Added new nsChannelBSC type.
[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_elem_tag(iter, brW)) {
665                 if(cspace)
666                     dompos_release(&last_space);
667                 cspace = '\n';
668
669                 nsIDOMNode_AddRef(iter);
670                 last_space.node = iter;
671                 last_space.type = ELEMENT_NODE;
672                 last_space.off = 0;
673                 last_space.p = NULL;
674             }else if(is_elem_tag(iter, hrW)) {
675                 if(cspace) {
676                     *new_pos = last_space;
677                     nsIDOMNode_Release(iter);
678                     return cspace;
679                 }
680
681                 new_pos->node = iter;
682                 new_pos->type = ELEMENT_NODE;
683                 new_pos->off = 0;
684                 new_pos->p = NULL;
685                 return '\n';
686             }
687         }
688
689         tmp = iter;
690         iter = next_node(iter);
691         nsIDOMNode_Release(tmp);
692     }
693
694     if(cspace) {
695         *new_pos = last_space;
696     }else {
697         *new_pos = *pos;
698         dompos_addref(new_pos);
699     }
700
701     return cspace;
702 }
703
704 static WCHAR prev_char(HTMLTxtRange *This, const dompos_t *pos, dompos_t *new_pos)
705 {
706     nsIDOMNode *iter, *tmp;
707     const WCHAR *p;
708     BOOL skip_space = FALSE;
709
710     if(pos->type == TEXT_NODE && isspaceW(pos->p[pos->off]))
711         skip_space = TRUE;
712
713     if(pos->type == TEXT_NODE && pos->off) {
714         p = pos->p+pos->off-1;
715
716         if(skip_space) {
717             while(p >= pos->p && isspace(*p))
718                 p--;
719         }
720
721         if(p >= pos->p) {
722             *new_pos = *pos;
723             new_pos->off = p-pos->p;
724             dompos_addref(new_pos);
725             return new_pos->p[new_pos->off];
726         }
727     }
728
729     iter = prev_node(This, pos->node);
730
731     while(iter) {
732         switch(get_node_type(iter)) {
733         case TEXT_NODE: {
734             dompos_t tmp_pos;
735
736             tmp_pos.node = iter;
737             tmp_pos.type = TEXT_NODE;
738             dompos_addref(&tmp_pos);
739
740             p = tmp_pos.p + strlenW(tmp_pos.p)-1;
741
742             if(skip_space) {
743                 while(p >= tmp_pos.p && isspaceW(*p))
744                     p--;
745             }
746
747             if(p < tmp_pos.p) {
748                 dompos_release(&tmp_pos);
749                 break;
750             }
751
752             tmp_pos.off = p-tmp_pos.p;
753             *new_pos = tmp_pos;
754             nsIDOMNode_Release(iter);
755             return *p;
756         }
757
758         case ELEMENT_NODE:
759             if(is_elem_tag(iter, brW)) {
760                 if(skip_space) {
761                     skip_space = FALSE;
762                     break;
763                 }
764             }else if(!is_elem_tag(iter, hrW)) {
765                 break;
766             }
767
768             new_pos->node = iter;
769             new_pos->type = ELEMENT_NODE;
770             new_pos->off = 0;
771             new_pos->p = NULL;
772             return '\n';
773         }
774
775         tmp = iter;
776         iter = prev_node(This, iter);
777         nsIDOMNode_Release(tmp);
778     }
779
780     *new_pos = *pos;
781     dompos_addref(new_pos);
782     return 0;
783 }
784
785 static long move_next_chars(long cnt, const dompos_t *pos, BOOL col, const dompos_t *bound_pos,
786         BOOL *bounded, dompos_t *new_pos)
787 {
788     dompos_t iter, tmp;
789     long ret = 0;
790     WCHAR c;
791
792     if(bounded)
793         *bounded = FALSE;
794
795     if(col)
796         ret++;
797
798     if(ret >= cnt) {
799         end_space(pos, new_pos);
800         return ret;
801     }
802
803     c = next_char(pos, &iter);
804     ret++;
805
806     while(ret < cnt) {
807         tmp = iter;
808         c = next_char(&tmp, &iter);
809         dompos_release(&tmp);
810         if(!c)
811             break;
812
813         ret++;
814         if(bound_pos && dompos_cmp(&tmp, bound_pos)) {
815             *bounded = TRUE;
816             ret++;
817         }
818     }
819
820     *new_pos = iter;
821     return ret;
822 }
823
824 static long move_prev_chars(HTMLTxtRange *This, long cnt, const dompos_t *pos, BOOL end,
825         const dompos_t *bound_pos, BOOL *bounded, dompos_t *new_pos)
826 {
827     dompos_t iter, tmp;
828     long ret = 0;
829     BOOL prev_eq = FALSE;
830     WCHAR c;
831
832     if(bounded)
833         *bounded = FALSE;
834
835     c = prev_char(This, pos, &iter);
836     if(c)
837         ret++;
838
839     while(c && ret < cnt) {
840         tmp = iter;
841         c = prev_char(This, &tmp, &iter);
842         dompos_release(&tmp);
843         if(!c) {
844             if(end)
845                 ret++;
846             break;
847         }
848
849         ret++;
850
851         if(prev_eq) {
852             *bounded = TRUE;
853             ret++;
854         }
855
856         prev_eq = bound_pos && dompos_cmp(&iter, bound_pos);
857     }
858
859     *new_pos = iter;
860     return ret;
861 }
862
863 static long find_prev_space(HTMLTxtRange *This, const dompos_t *pos, BOOL first_space, dompos_t *ret)
864 {
865     dompos_t iter, tmp;
866     WCHAR c;
867
868     c = prev_char(This, pos, &iter);
869     if(!c || (first_space && isspaceW(c))) {
870         *ret = iter;
871         return FALSE;
872     }
873
874     while(1) {
875         tmp = iter;
876         c = prev_char(This, &tmp, &iter);
877         if(!c || isspaceW(c)) {
878             dompos_release(&iter);
879             break;
880         }
881         dompos_release(&tmp);
882     }
883
884     *ret = tmp;
885     return TRUE;
886 }
887
888 static int find_word_end(const dompos_t *pos, dompos_t *ret)
889 {
890     dompos_t iter, tmp;
891     int cnt = 1;
892     WCHAR c;
893     c = get_pos_char(pos);
894     if(isspaceW(c)) {
895         *ret = *pos;
896         dompos_addref(ret);
897         return 0;
898     }
899
900     c = next_char(pos, &iter);
901     if(!c) {
902         *ret = iter;
903         return 0;
904     }
905     if(c == '\n') {
906         *ret = *pos;
907         dompos_addref(ret);
908         return 0;
909     }
910
911     while(c && !isspaceW(c)) {
912         tmp = iter;
913         c = next_char(&tmp, &iter);
914         if(c == '\n') {
915             dompos_release(&iter);
916             iter = tmp;
917         }else {
918             cnt++;
919             dompos_release(&tmp);
920         }
921     }
922
923     *ret = iter;
924     return cnt;
925 }
926
927 static long move_next_words(long cnt, const dompos_t *pos, dompos_t *new_pos)
928 {
929     dompos_t iter, tmp;
930     long ret = 0;
931     WCHAR c;
932
933     c = get_pos_char(pos);
934     if(isspaceW(c)) {
935         end_space(pos, &iter);
936         ret++;
937     }else {
938         c = next_char(pos, &iter);
939         if(c && isspaceW(c))
940             ret++;
941     }
942
943     while(c && ret < cnt) {
944         tmp = iter;
945         c = next_char(&tmp, &iter);
946         dompos_release(&tmp);
947         if(isspaceW(c))
948             ret++;
949     }
950
951     *new_pos = iter;
952     return ret;
953 }
954
955 static long move_prev_words(HTMLTxtRange *This, long cnt, const dompos_t *pos, dompos_t *new_pos)
956 {
957     dompos_t iter, tmp;
958     long ret = 0;
959
960     iter = *pos;
961     dompos_addref(&iter);
962
963     while(ret < cnt) {
964         if(!find_prev_space(This, &iter, FALSE, &tmp))
965             break;
966
967         dompos_release(&iter);
968         iter = tmp;
969         ret++;
970     }
971
972     *new_pos = iter;
973     return ret;
974 }
975
976 #define HTMLTXTRANGE_THIS(iface) DEFINE_THIS(HTMLTxtRange, HTMLTxtRange, iface)
977
978 static HRESULT WINAPI HTMLTxtRange_QueryInterface(IHTMLTxtRange *iface, REFIID riid, void **ppv)
979 {
980     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
981
982     *ppv = NULL;
983
984     if(IsEqualGUID(&IID_IUnknown, riid)) {
985         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
986         *ppv = HTMLTXTRANGE(This);
987     }else if(IsEqualGUID(&IID_IDispatch, riid)) {
988         TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
989         *ppv = HTMLTXTRANGE(This);
990     }else if(IsEqualGUID(&IID_IHTMLTxtRange, riid)) {
991         TRACE("(%p)->(IID_IHTMLTxtRange %p)\n", This, ppv);
992         *ppv = HTMLTXTRANGE(This);
993     }else if(IsEqualGUID(&IID_IOleCommandTarget, riid)) {
994         TRACE("(%p)->(IID_IOleCommandTarget %p)\n", This, ppv);
995         *ppv = CMDTARGET(This);
996     }
997
998     if(*ppv) {
999         IUnknown_AddRef((IUnknown*)*ppv);
1000         return S_OK;
1001     }
1002
1003     WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
1004     return E_NOINTERFACE;
1005 }
1006
1007 static ULONG WINAPI HTMLTxtRange_AddRef(IHTMLTxtRange *iface)
1008 {
1009     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1010     LONG ref = InterlockedIncrement(&This->ref);
1011
1012     TRACE("(%p) ref=%d\n", This, ref);
1013
1014     return ref;
1015 }
1016
1017 static ULONG WINAPI HTMLTxtRange_Release(IHTMLTxtRange *iface)
1018 {
1019     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1020     LONG ref = InterlockedDecrement(&This->ref);
1021
1022     TRACE("(%p) ref=%d\n", This, ref);
1023
1024     if(!ref) {
1025         if(This->nsrange)
1026             nsISelection_Release(This->nsrange);
1027         if(This->doc)
1028             list_remove(&This->entry);
1029         heap_free(This);
1030     }
1031
1032     return ref;
1033 }
1034
1035 static HRESULT WINAPI HTMLTxtRange_GetTypeInfoCount(IHTMLTxtRange *iface, UINT *pctinfo)
1036 {
1037     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1038     FIXME("(%p)->(%p)\n", This, pctinfo);
1039     return E_NOTIMPL;
1040 }
1041
1042 static HRESULT WINAPI HTMLTxtRange_GetTypeInfo(IHTMLTxtRange *iface, UINT iTInfo,
1043                                                LCID lcid, ITypeInfo **ppTInfo)
1044 {
1045     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1046     FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
1047     return E_NOTIMPL;
1048 }
1049
1050 static HRESULT WINAPI HTMLTxtRange_GetIDsOfNames(IHTMLTxtRange *iface, REFIID riid,
1051                                                  LPOLESTR *rgszNames, UINT cNames,
1052                                                  LCID lcid, DISPID *rgDispId)
1053 {
1054     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1055     FIXME("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
1056           lcid, rgDispId);
1057     return E_NOTIMPL;
1058 }
1059
1060 static HRESULT WINAPI HTMLTxtRange_Invoke(IHTMLTxtRange *iface, DISPID dispIdMember,
1061                             REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
1062                             VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
1063 {
1064     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1065     FIXME("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
1066           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
1067     return E_NOTIMPL;
1068 }
1069
1070 static HRESULT WINAPI HTMLTxtRange_get_htmlText(IHTMLTxtRange *iface, BSTR *p)
1071 {
1072     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1073
1074     TRACE("(%p)->(%p)\n", This, p);
1075
1076     *p = NULL;
1077
1078     if(This->nsrange) {
1079         nsIDOMDocumentFragment *fragment;
1080         nsresult nsres;
1081
1082         nsres = nsIDOMRange_CloneContents(This->nsrange, &fragment);
1083         if(NS_SUCCEEDED(nsres)) {
1084             const PRUnichar *nstext;
1085             nsAString nsstr;
1086
1087             nsAString_Init(&nsstr, NULL);
1088             nsnode_to_nsstring((nsIDOMNode*)fragment, &nsstr);
1089             nsIDOMDocumentFragment_Release(fragment);
1090
1091             nsAString_GetData(&nsstr, &nstext);
1092             *p = SysAllocString(nstext);
1093
1094             nsAString_Finish(&nsstr);
1095         }
1096     }
1097
1098     if(!*p) {
1099         const WCHAR emptyW[] = {0};
1100         *p = SysAllocString(emptyW);
1101     }
1102
1103     TRACE("return %s\n", debugstr_w(*p));
1104     return S_OK;
1105 }
1106
1107 static HRESULT WINAPI HTMLTxtRange_put_text(IHTMLTxtRange *iface, BSTR v)
1108 {
1109     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1110     nsIDOMDocument *nsdoc;
1111     nsIDOMText *text_node;
1112     nsAString text_str;
1113     nsresult nsres;
1114
1115     TRACE("(%p)->(%s)\n", This, debugstr_w(v));
1116
1117     if(!This->doc)
1118         return MSHTML_E_NODOC;
1119
1120     nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1121     if(NS_FAILED(nsres)) {
1122         ERR("GetDocument failed: %08x\n", nsres);
1123         return S_OK;
1124     }
1125
1126     nsAString_Init(&text_str, v);
1127     nsres = nsIDOMDocument_CreateTextNode(nsdoc, &text_str, &text_node);
1128     nsIDOMDocument_Release(nsdoc);
1129     nsAString_Finish(&text_str);
1130     if(NS_FAILED(nsres)) {
1131         ERR("CreateTextNode failed: %08x\n", nsres);
1132         return S_OK;
1133     }
1134     nsres = nsIDOMRange_DeleteContents(This->nsrange);
1135     if(NS_FAILED(nsres))
1136         ERR("DeleteContents failed: %08x\n", nsres);
1137
1138     nsres = nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)text_node);
1139     if(NS_FAILED(nsres))
1140         ERR("InsertNode failed: %08x\n", nsres);
1141
1142     nsres = nsIDOMRange_SetEndAfter(This->nsrange, (nsIDOMNode*)text_node);
1143     if(NS_FAILED(nsres))
1144         ERR("SetEndAfter failed: %08x\n", nsres);
1145
1146     return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), VARIANT_FALSE);
1147 }
1148
1149 static HRESULT WINAPI HTMLTxtRange_get_text(IHTMLTxtRange *iface, BSTR *p)
1150 {
1151     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1152     wstrbuf_t buf;
1153
1154     TRACE("(%p)->(%p)\n", This, p);
1155
1156     *p = NULL;
1157     if(!This->nsrange)
1158         return S_OK;
1159
1160     wstrbuf_init(&buf);
1161     range_to_string(This, &buf);
1162     if(buf.buf)
1163         *p = SysAllocString(buf.buf);
1164     wstrbuf_finish(&buf);
1165
1166     TRACE("ret %s\n", debugstr_w(*p));
1167     return S_OK;
1168 }
1169
1170 static HRESULT WINAPI HTMLTxtRange_parentElement(IHTMLTxtRange *iface, IHTMLElement **parent)
1171 {
1172     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1173     nsIDOMNode *nsnode, *tmp;
1174     HTMLDOMNode *node;
1175
1176     TRACE("(%p)->(%p)\n", This, parent);
1177
1178     nsIDOMRange_GetCommonAncestorContainer(This->nsrange, &nsnode);
1179     while(nsnode && get_node_type(nsnode) != ELEMENT_NODE) {
1180         nsIDOMNode_GetParentNode(nsnode, &tmp);
1181         nsIDOMNode_Release(nsnode);
1182         nsnode = tmp;
1183     }
1184
1185     if(!nsnode) {
1186         *parent = NULL;
1187         return S_OK;
1188     }
1189
1190     node = get_node(This->doc, nsnode);
1191     nsIDOMNode_Release(nsnode);
1192
1193     return IHTMLDOMNode_QueryInterface(HTMLDOMNODE(node), &IID_IHTMLElement, (void**)parent);
1194 }
1195
1196 static HRESULT WINAPI HTMLTxtRange_duplicate(IHTMLTxtRange *iface, IHTMLTxtRange **Duplicate)
1197 {
1198     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1199     nsIDOMRange *nsrange = NULL;
1200
1201     TRACE("(%p)->(%p)\n", This, Duplicate);
1202
1203     nsIDOMRange_CloneRange(This->nsrange, &nsrange);
1204     *Duplicate = HTMLTxtRange_Create(This->doc, nsrange);
1205     nsIDOMRange_Release(nsrange);
1206
1207     return S_OK;
1208 }
1209
1210 static HRESULT WINAPI HTMLTxtRange_inRange(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1211         VARIANT_BOOL *InRange)
1212 {
1213     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1214     HTMLTxtRange *src_range;
1215     PRInt16 nsret = 0;
1216     nsresult nsres;
1217
1218     TRACE("(%p)->(%p %p)\n", This, Range, InRange);
1219
1220     *InRange = VARIANT_FALSE;
1221
1222     src_range = get_range_object(This->doc, Range);
1223     if(!src_range)
1224         return E_FAIL;
1225
1226     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1227             src_range->nsrange, &nsret);
1228     if(NS_SUCCEEDED(nsres) && nsret <= 0) {
1229         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1230                 src_range->nsrange, &nsret);
1231         if(NS_SUCCEEDED(nsres) && nsret >= 0)
1232             *InRange = VARIANT_TRUE;
1233     }
1234
1235     if(NS_FAILED(nsres))
1236         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1237
1238     return S_OK;
1239 }
1240
1241 static HRESULT WINAPI HTMLTxtRange_isEqual(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1242         VARIANT_BOOL *IsEqual)
1243 {
1244     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1245     HTMLTxtRange *src_range;
1246     PRInt16 nsret = 0;
1247     nsresult nsres;
1248
1249     TRACE("(%p)->(%p %p)\n", This, Range, IsEqual);
1250
1251     *IsEqual = VARIANT_FALSE;
1252
1253     src_range = get_range_object(This->doc, Range);
1254     if(!src_range)
1255         return E_FAIL;
1256
1257     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1258             src_range->nsrange, &nsret);
1259     if(NS_SUCCEEDED(nsres) && !nsret) {
1260         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1261                 src_range->nsrange, &nsret);
1262         if(NS_SUCCEEDED(nsres) && !nsret)
1263             *IsEqual = VARIANT_TRUE;
1264     }
1265
1266     if(NS_FAILED(nsres))
1267         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1268
1269     return S_OK;
1270 }
1271
1272 static HRESULT WINAPI HTMLTxtRange_scrollIntoView(IHTMLTxtRange *iface, VARIANT_BOOL fStart)
1273 {
1274     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1275     FIXME("(%p)->(%x)\n", This, fStart);
1276     return E_NOTIMPL;
1277 }
1278
1279 static HRESULT WINAPI HTMLTxtRange_collapse(IHTMLTxtRange *iface, VARIANT_BOOL Start)
1280 {
1281     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1282
1283     TRACE("(%p)->(%x)\n", This, Start);
1284
1285     nsIDOMRange_Collapse(This->nsrange, Start != VARIANT_FALSE);
1286     return S_OK;
1287 }
1288
1289 static HRESULT WINAPI HTMLTxtRange_expand(IHTMLTxtRange *iface, BSTR Unit, VARIANT_BOOL *Success)
1290 {
1291     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1292     range_unit_t unit;
1293
1294     TRACE("(%p)->(%s %p)\n", This, debugstr_w(Unit), Success);
1295
1296     unit = string_to_unit(Unit);
1297     if(unit == RU_UNKNOWN)
1298         return E_INVALIDARG;
1299
1300     *Success = VARIANT_FALSE;
1301
1302     switch(unit) {
1303     case RU_WORD: {
1304         dompos_t end_pos, start_pos, new_start_pos, new_end_pos;
1305         PRBool collapsed;
1306
1307         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1308
1309         get_cur_pos(This, TRUE, &start_pos);
1310         get_cur_pos(This, FALSE, &end_pos);
1311
1312         if(find_word_end(&end_pos, &new_end_pos) || collapsed) {
1313             set_range_pos(This, FALSE, &new_end_pos);
1314             *Success = VARIANT_TRUE;
1315         }
1316
1317         if(start_pos.type && (get_pos_char(&end_pos) || !dompos_cmp(&new_end_pos, &end_pos))) {
1318             if(find_prev_space(This, &start_pos, TRUE, &new_start_pos)) {
1319                 set_range_pos(This, TRUE, &new_start_pos);
1320                 *Success = VARIANT_TRUE;
1321             }
1322             dompos_release(&new_start_pos);
1323         }
1324
1325         dompos_release(&new_end_pos);
1326         dompos_release(&end_pos);
1327         dompos_release(&start_pos);
1328
1329         break;
1330     }
1331
1332     case RU_TEXTEDIT: {
1333         nsIDOMDocument *nsdoc;
1334         nsIDOMHTMLDocument *nshtmldoc;
1335         nsIDOMHTMLElement *nsbody = NULL;
1336         nsresult nsres;
1337
1338         nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1339         if(NS_FAILED(nsres) || !nsdoc) {
1340             ERR("GetDocument failed: %08x\n", nsres);
1341             break;
1342         }
1343
1344         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
1345         nsIDOMDocument_Release(nsdoc);
1346
1347         nsres = nsIDOMHTMLDocument_GetBody(nshtmldoc, &nsbody);
1348         nsIDOMHTMLDocument_Release(nshtmldoc);
1349         if(NS_FAILED(nsres) || !nsbody) {
1350             ERR("Could not get body: %08x\n", nsres);
1351             break;
1352         }
1353
1354         nsres = nsIDOMRange_SelectNodeContents(This->nsrange, (nsIDOMNode*)nsbody);
1355         nsIDOMHTMLElement_Release(nsbody);
1356         if(NS_FAILED(nsres)) {
1357             ERR("Collapse failed: %08x\n", nsres);
1358             break;
1359         }
1360
1361         *Success = VARIANT_TRUE;
1362         break;
1363     }
1364
1365     default:
1366         FIXME("Unimplemented unit %s\n", debugstr_w(Unit));
1367     }
1368
1369     return S_OK;
1370 }
1371
1372 static HRESULT WINAPI HTMLTxtRange_move(IHTMLTxtRange *iface, BSTR Unit,
1373         long Count, long *ActualCount)
1374 {
1375     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1376     range_unit_t unit;
1377
1378     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1379
1380     unit = string_to_unit(Unit);
1381     if(unit == RU_UNKNOWN)
1382         return E_INVALIDARG;
1383
1384     if(!Count) {
1385         *ActualCount = 0;
1386         return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1387     }
1388
1389     switch(unit) {
1390     case RU_CHAR: {
1391         dompos_t cur_pos, new_pos;
1392
1393         get_cur_pos(This, TRUE, &cur_pos);
1394
1395         if(Count > 0) {
1396             *ActualCount = move_next_chars(Count, &cur_pos, TRUE, NULL, NULL, &new_pos);
1397             set_range_pos(This, FALSE, &new_pos);
1398             dompos_release(&new_pos);
1399
1400             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1401         }else {
1402             *ActualCount = -move_prev_chars(This, -Count, &cur_pos, FALSE, NULL, NULL, &new_pos);
1403             set_range_pos(This, TRUE, &new_pos);
1404             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1405             dompos_release(&new_pos);
1406         }
1407
1408         dompos_release(&cur_pos);
1409         break;
1410     }
1411
1412     case RU_WORD: {
1413         dompos_t cur_pos, new_pos;
1414
1415         get_cur_pos(This, TRUE, &cur_pos);
1416
1417         if(Count > 0) {
1418             *ActualCount = move_next_words(Count, &cur_pos, &new_pos);
1419             set_range_pos(This, FALSE, &new_pos);
1420             dompos_release(&new_pos);
1421             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1422         }else {
1423             *ActualCount = -move_prev_words(This, -Count, &cur_pos, &new_pos);
1424             set_range_pos(This, TRUE, &new_pos);
1425             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1426             dompos_release(&new_pos);
1427         }
1428
1429         dompos_release(&cur_pos);
1430         break;
1431     }
1432
1433     default:
1434         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1435     }
1436
1437     TRACE("ret %ld\n", *ActualCount);
1438     return S_OK;
1439 }
1440
1441 static HRESULT WINAPI HTMLTxtRange_moveStart(IHTMLTxtRange *iface, BSTR Unit,
1442         long Count, long *ActualCount)
1443 {
1444     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1445     range_unit_t unit;
1446
1447     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1448
1449     unit = string_to_unit(Unit);
1450     if(unit == RU_UNKNOWN)
1451         return E_INVALIDARG;
1452
1453     if(!Count) {
1454         *ActualCount = 0;
1455         return S_OK;
1456     }
1457
1458     switch(unit) {
1459     case RU_CHAR: {
1460         dompos_t start_pos, end_pos, new_pos;
1461         PRBool collapsed;
1462
1463         get_cur_pos(This, TRUE, &start_pos);
1464         get_cur_pos(This, FALSE, &end_pos);
1465         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1466
1467         if(Count > 0) {
1468             BOOL bounded;
1469
1470             *ActualCount = move_next_chars(Count, &start_pos, collapsed, &end_pos, &bounded, &new_pos);
1471             set_range_pos(This, !bounded, &new_pos);
1472             if(bounded)
1473                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1474         }else {
1475             *ActualCount = -move_prev_chars(This, -Count, &start_pos, FALSE, NULL, NULL, &new_pos);
1476             set_range_pos(This, TRUE, &new_pos);
1477         }
1478
1479         dompos_release(&start_pos);
1480         dompos_release(&end_pos);
1481         dompos_release(&new_pos);
1482         break;
1483     }
1484
1485     default:
1486         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1487     }
1488
1489     return S_OK;
1490 }
1491
1492 static HRESULT WINAPI HTMLTxtRange_moveEnd(IHTMLTxtRange *iface, BSTR Unit,
1493         long Count, long *ActualCount)
1494 {
1495     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1496     range_unit_t unit;
1497
1498     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1499
1500     unit = string_to_unit(Unit);
1501     if(unit == RU_UNKNOWN)
1502         return E_INVALIDARG;
1503
1504     if(!Count) {
1505         *ActualCount = 0;
1506         return S_OK;
1507     }
1508
1509     switch(unit) {
1510     case RU_CHAR: {
1511         dompos_t start_pos, end_pos, new_pos;
1512         PRBool collapsed;
1513
1514         get_cur_pos(This, TRUE, &start_pos);
1515         get_cur_pos(This, FALSE, &end_pos);
1516         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1517
1518         if(Count > 0) {
1519             *ActualCount = move_next_chars(Count, &end_pos, collapsed, NULL, NULL, &new_pos);
1520             set_range_pos(This, FALSE, &new_pos);
1521         }else {
1522             BOOL bounded;
1523
1524             *ActualCount = -move_prev_chars(This, -Count, &end_pos, TRUE, &start_pos, &bounded, &new_pos);
1525             set_range_pos(This, bounded, &new_pos);
1526             if(bounded)
1527                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1528         }
1529
1530         dompos_release(&start_pos);
1531         dompos_release(&end_pos);
1532         dompos_release(&new_pos);
1533         break;
1534     }
1535
1536     default:
1537         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1538     }
1539
1540     return S_OK;
1541 }
1542
1543 static HRESULT WINAPI HTMLTxtRange_select(IHTMLTxtRange *iface)
1544 {
1545     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1546
1547     TRACE("(%p)\n", This);
1548
1549     if(This->doc->nscontainer) {
1550         nsIDOMWindow *dom_window = NULL;
1551         nsISelection *nsselection;
1552
1553         nsIWebBrowser_GetContentDOMWindow(This->doc->nscontainer->webbrowser, &dom_window);
1554         nsIDOMWindow_GetSelection(dom_window, &nsselection);
1555         nsIDOMWindow_Release(dom_window);
1556
1557         nsISelection_RemoveAllRanges(nsselection);
1558         nsISelection_AddRange(nsselection, This->nsrange);
1559
1560         nsISelection_Release(nsselection);
1561     }
1562
1563     return S_OK;
1564 }
1565
1566 static HRESULT WINAPI HTMLTxtRange_pasteHTML(IHTMLTxtRange *iface, BSTR html)
1567 {
1568     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1569     FIXME("(%p)->(%s)\n", This, debugstr_w(html));
1570     return E_NOTIMPL;
1571 }
1572
1573 static HRESULT WINAPI HTMLTxtRange_moveToElementText(IHTMLTxtRange *iface, IHTMLElement *element)
1574 {
1575     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1576     FIXME("(%p)->(%p)\n", This, element);
1577     return E_NOTIMPL;
1578 }
1579
1580 static HRESULT WINAPI HTMLTxtRange_setEndPoint(IHTMLTxtRange *iface, BSTR how,
1581         IHTMLTxtRange *SourceRange)
1582 {
1583     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1584     FIXME("(%p)->(%s %p)\n", This, debugstr_w(how), SourceRange);
1585     return E_NOTIMPL;
1586 }
1587
1588 static HRESULT WINAPI HTMLTxtRange_compareEndPoints(IHTMLTxtRange *iface, BSTR how,
1589         IHTMLTxtRange *SourceRange, long *ret)
1590 {
1591     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1592     HTMLTxtRange *src_range;
1593     PRInt16 nsret = 0;
1594     int nscmpt;
1595     nsresult nsres;
1596
1597     TRACE("(%p)->(%s %p %p)\n", This, debugstr_w(how), SourceRange, ret);
1598
1599     nscmpt = string_to_nscmptype(how);
1600     if(nscmpt == -1)
1601         return E_INVALIDARG;
1602
1603     src_range = get_range_object(This->doc, SourceRange);
1604     if(!src_range)
1605         return E_FAIL;
1606
1607     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, nscmpt, src_range->nsrange, &nsret);
1608     if(NS_FAILED(nsres))
1609         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1610
1611     *ret = nsret;
1612     return S_OK;
1613 }
1614
1615 static HRESULT WINAPI HTMLTxtRange_findText(IHTMLTxtRange *iface, BSTR String,
1616         long count, long Flags, VARIANT_BOOL *Success)
1617 {
1618     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1619     FIXME("(%p)->(%s %ld %08lx %p)\n", This, debugstr_w(String), count, Flags, Success);
1620     return E_NOTIMPL;
1621 }
1622
1623 static HRESULT WINAPI HTMLTxtRange_moveToPoint(IHTMLTxtRange *iface, long x, long y)
1624 {
1625     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1626     FIXME("(%p)->(%ld %ld)\n", This, x, y);
1627     return E_NOTIMPL;
1628 }
1629
1630 static HRESULT WINAPI HTMLTxtRange_getBookmark(IHTMLTxtRange *iface, BSTR *Bookmark)
1631 {
1632     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1633     FIXME("(%p)->(%p)\n", This, Bookmark);
1634     return E_NOTIMPL;
1635 }
1636
1637 static HRESULT WINAPI HTMLTxtRange_moveToBookmark(IHTMLTxtRange *iface, BSTR Bookmark,
1638         VARIANT_BOOL *Success)
1639 {
1640     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1641     FIXME("(%p)->(%s %p)\n", This, debugstr_w(Bookmark), Success);
1642     return E_NOTIMPL;
1643 }
1644
1645 static HRESULT WINAPI HTMLTxtRange_queryCommandSupported(IHTMLTxtRange *iface, BSTR cmdID,
1646         VARIANT_BOOL *pfRet)
1647 {
1648     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1649     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1650     return E_NOTIMPL;
1651 }
1652
1653 static HRESULT WINAPI HTMLTxtRange_queryCommandEnabled(IHTMLTxtRange *iface, BSTR cmdID,
1654         VARIANT_BOOL *pfRet)
1655 {
1656     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1657     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1658     return E_NOTIMPL;
1659 }
1660
1661 static HRESULT WINAPI HTMLTxtRange_queryCommandState(IHTMLTxtRange *iface, BSTR cmdID,
1662         VARIANT_BOOL *pfRet)
1663 {
1664     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1665     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1666     return E_NOTIMPL;
1667 }
1668
1669 static HRESULT WINAPI HTMLTxtRange_queryCommandIndeterm(IHTMLTxtRange *iface, BSTR cmdID,
1670         VARIANT_BOOL *pfRet)
1671 {
1672     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1673     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1674     return E_NOTIMPL;
1675 }
1676
1677 static HRESULT WINAPI HTMLTxtRange_queryCommandText(IHTMLTxtRange *iface, BSTR cmdID,
1678         BSTR *pcmdText)
1679 {
1680     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1681     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdText);
1682     return E_NOTIMPL;
1683 }
1684
1685 static HRESULT WINAPI HTMLTxtRange_queryCommandValue(IHTMLTxtRange *iface, BSTR cmdID,
1686         VARIANT *pcmdValue)
1687 {
1688     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1689     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdValue);
1690     return E_NOTIMPL;
1691 }
1692
1693 static HRESULT WINAPI HTMLTxtRange_execCommand(IHTMLTxtRange *iface, BSTR cmdID,
1694         VARIANT_BOOL showUI, VARIANT value, VARIANT_BOOL *pfRet)
1695 {
1696     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1697     FIXME("(%p)->(%s %x v %p)\n", This, debugstr_w(cmdID), showUI, pfRet);
1698     return E_NOTIMPL;
1699 }
1700
1701 static HRESULT WINAPI HTMLTxtRange_execCommandShowHelp(IHTMLTxtRange *iface, BSTR cmdID,
1702         VARIANT_BOOL *pfRet)
1703 {
1704     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1705     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1706     return E_NOTIMPL;
1707 }
1708
1709 #undef HTMLTXTRANGE_THIS
1710
1711 static const IHTMLTxtRangeVtbl HTMLTxtRangeVtbl = {
1712     HTMLTxtRange_QueryInterface,
1713     HTMLTxtRange_AddRef,
1714     HTMLTxtRange_Release,
1715     HTMLTxtRange_GetTypeInfoCount,
1716     HTMLTxtRange_GetTypeInfo,
1717     HTMLTxtRange_GetIDsOfNames,
1718     HTMLTxtRange_Invoke,
1719     HTMLTxtRange_get_htmlText,
1720     HTMLTxtRange_put_text,
1721     HTMLTxtRange_get_text,
1722     HTMLTxtRange_parentElement,
1723     HTMLTxtRange_duplicate,
1724     HTMLTxtRange_inRange,
1725     HTMLTxtRange_isEqual,
1726     HTMLTxtRange_scrollIntoView,
1727     HTMLTxtRange_collapse,
1728     HTMLTxtRange_expand,
1729     HTMLTxtRange_move,
1730     HTMLTxtRange_moveStart,
1731     HTMLTxtRange_moveEnd,
1732     HTMLTxtRange_select,
1733     HTMLTxtRange_pasteHTML,
1734     HTMLTxtRange_moveToElementText,
1735     HTMLTxtRange_setEndPoint,
1736     HTMLTxtRange_compareEndPoints,
1737     HTMLTxtRange_findText,
1738     HTMLTxtRange_moveToPoint,
1739     HTMLTxtRange_getBookmark,
1740     HTMLTxtRange_moveToBookmark,
1741     HTMLTxtRange_queryCommandSupported,
1742     HTMLTxtRange_queryCommandEnabled,
1743     HTMLTxtRange_queryCommandState,
1744     HTMLTxtRange_queryCommandIndeterm,
1745     HTMLTxtRange_queryCommandText,
1746     HTMLTxtRange_queryCommandValue,
1747     HTMLTxtRange_execCommand,
1748     HTMLTxtRange_execCommandShowHelp
1749 };
1750
1751 #define OLECMDTRG_THIS(iface) DEFINE_THIS(HTMLTxtRange, OleCommandTarget, iface)
1752
1753 static HRESULT WINAPI RangeCommandTarget_QueryInterface(IOleCommandTarget *iface, REFIID riid, void **ppv)
1754 {
1755     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1756     return IHTMLTxtRange_QueryInterface(HTMLTXTRANGE(This), riid, ppv);
1757 }
1758
1759 static ULONG WINAPI RangeCommandTarget_AddRef(IOleCommandTarget *iface)
1760 {
1761     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1762     return IHTMLTxtRange_AddRef(HTMLTXTRANGE(This));
1763 }
1764
1765 static ULONG WINAPI RangeCommandTarget_Release(IOleCommandTarget *iface)
1766 {
1767     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1768     return IHTMLTxtRange_Release(HTMLTXTRANGE(This));
1769 }
1770
1771 static HRESULT WINAPI RangeCommandTarget_QueryStatus(IOleCommandTarget *iface, const GUID *pguidCmdGroup,
1772         ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText)
1773 {
1774     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1775     FIXME("(%p)->(%s %d %p %p)\n", This, debugstr_guid(pguidCmdGroup), cCmds, prgCmds, pCmdText);
1776     return E_NOTIMPL;
1777 }
1778
1779 static HRESULT exec_indent(HTMLTxtRange *This, VARIANT *in, VARIANT *out)
1780 {
1781     nsIDOMDocumentFragment *fragment;
1782     nsIDOMElement *blockquote_elem, *p_elem;
1783     nsIDOMDocument *nsdoc;
1784     nsIDOMNode *tmp;
1785     nsAString tag_str;
1786
1787     static const PRUnichar blockquoteW[] = {'B','L','O','C','K','Q','U','O','T','E',0};
1788     static const PRUnichar pW[] = {'P',0};
1789
1790     TRACE("(%p)->(%p %p)\n", This, in, out);
1791
1792     nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1793
1794     nsAString_Init(&tag_str, blockquoteW);
1795     nsIDOMDocument_CreateElement(nsdoc, &tag_str, &blockquote_elem);
1796     nsAString_Finish(&tag_str);
1797
1798     nsAString_Init(&tag_str, pW);
1799     nsIDOMDocument_CreateElement(nsdoc, &tag_str, &p_elem);
1800     nsAString_Finish(&tag_str);
1801
1802     nsIDOMDocument_Release(nsdoc);
1803
1804     nsIDOMRange_ExtractContents(This->nsrange, &fragment);
1805     nsIDOMElement_AppendChild(p_elem, (nsIDOMNode*)fragment, &tmp);
1806     nsIDOMDocumentFragment_Release(fragment);
1807     nsIDOMNode_Release(tmp);
1808
1809     nsIDOMElement_AppendChild(blockquote_elem, (nsIDOMNode*)p_elem, &tmp);
1810     nsIDOMElement_Release(p_elem);
1811     nsIDOMNode_Release(tmp);
1812
1813     nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)blockquote_elem);
1814     nsIDOMElement_Release(blockquote_elem);
1815
1816     return S_OK;
1817 }
1818
1819 static HRESULT WINAPI RangeCommandTarget_Exec(IOleCommandTarget *iface, const GUID *pguidCmdGroup,
1820         DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
1821 {
1822     HTMLTxtRange *This = OLECMDTRG_THIS(iface);
1823
1824     TRACE("(%p)->(%s %d %x %p %p)\n", This, debugstr_guid(pguidCmdGroup), nCmdID,
1825           nCmdexecopt, pvaIn, pvaOut);
1826
1827     if(pguidCmdGroup && IsEqualGUID(&CGID_MSHTML, pguidCmdGroup)) {
1828         switch(nCmdID) {
1829         case IDM_INDENT:
1830             return exec_indent(This, pvaIn, pvaOut);
1831         default:
1832             FIXME("Unsupported cmdid %d of CGID_MSHTML\n", nCmdID);
1833         }
1834     }else {
1835         FIXME("Unsupported cmd %d of group %s\n", nCmdID, debugstr_guid(pguidCmdGroup));
1836     }
1837
1838     return E_NOTIMPL;
1839 }
1840
1841 #undef OLECMDTRG_THIS
1842
1843 static const IOleCommandTargetVtbl OleCommandTargetVtbl = {
1844     RangeCommandTarget_QueryInterface,
1845     RangeCommandTarget_AddRef,
1846     RangeCommandTarget_Release,
1847     RangeCommandTarget_QueryStatus,
1848     RangeCommandTarget_Exec
1849 };
1850
1851 IHTMLTxtRange *HTMLTxtRange_Create(HTMLDocument *doc, nsIDOMRange *nsrange)
1852 {
1853     HTMLTxtRange *ret = heap_alloc(sizeof(HTMLTxtRange));
1854
1855     ret->lpHTMLTxtRangeVtbl = &HTMLTxtRangeVtbl;
1856     ret->lpOleCommandTargetVtbl = &OleCommandTargetVtbl;
1857     ret->ref = 1;
1858
1859     if(nsrange)
1860         nsIDOMRange_AddRef(nsrange);
1861     ret->nsrange = nsrange;
1862
1863     ret->doc = doc;
1864     list_add_head(&doc->range_list, &ret->entry);
1865
1866     return HTMLTXTRANGE(ret);
1867 }
1868
1869 void detach_ranges(HTMLDocument *This)
1870 {
1871     HTMLTxtRange *iter;
1872
1873     LIST_FOR_EACH_ENTRY(iter, &This->range_list, HTMLTxtRange, entry) {
1874         iter->doc = NULL;
1875     }
1876 }