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