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