2007-12-12 Johannes Schmid <jhs@gnome.org>
[anjuta-git-plugin.git] / plugins / search / search-replace_backend.c
blob1d3d29ffadd4d0c8a606798b503f9b01b6bda63a
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
3 /*
4 ** search-replace_backend.c: Generic Search and Replace
5 ** Author: Biswapesh Chattopadhyay
6 */
8 /*
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Library General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <dirent.h>
35 #include <fnmatch.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
40 #include <gnome.h>
41 #include <glade/glade.h>
42 #include <libgnomevfs/gnome-vfs.h>
44 #include <libanjuta/anjuta-utils.h>
45 #include <libanjuta/anjuta-shell.h>
46 #include <libanjuta/anjuta-plugin.h>
47 #include <libanjuta/anjuta-debug.h>
48 #include <libanjuta/interfaces/ianjuta-editor.h>
49 #include <libanjuta/interfaces/ianjuta-document.h>
50 #include <libanjuta/interfaces/ianjuta-file.h>
51 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
52 #include <libanjuta/interfaces/ianjuta-project-manager.h>
55 #define GTK
56 #undef PLAT_GTK
57 #define PLAT_GTK 1
58 #include "Scintilla.h"
59 #include "SciLexer.h"
60 #include "ScintillaWidget.h"
63 #include "search-replace_backend.h"
64 #include "tm_tagmanager.h"
66 /* Information about a matched substring */
67 typedef struct _MatchSubStr
69 gint start;
70 gint len;
71 } MatchSubStr;
73 static SearchReplace *sr = NULL;
75 void clear_search_replace_instance(void);
78 static void
79 pcre_info_free (PcreInfo *re)
81 if (re)
83 if (re->re)
84 (*pcre_free)(re->re);
85 if (re->extra)
86 (*pcre_free)(re->extra);
87 if (re->ovector)
88 g_free(re->ovector);
89 g_free(re);
93 static PcreInfo *
94 pcre_info_new (SearchExpression *s)
96 PcreInfo *re;
97 int options = 0;
98 const char *err;
99 int err_offset;
100 int status;
102 g_return_val_if_fail(s && s->search_str, NULL);
103 re = g_new0(PcreInfo, 1);
104 if (s->ignore_case)
105 options |= PCRE_CASELESS;
106 if (!s->greedy)
107 options |= PCRE_UNGREEDY;
108 re->re = pcre_compile(s->search_str, options, &err, &err_offset, NULL);
109 if (NULL == re->re)
111 /* Compile failed - check error message */
112 g_warning("Regex compile failed! %s at position %d", err, err_offset);
113 pcre_info_free(re);
114 return NULL;
116 re->extra = pcre_study(re->re, 0, &err);
117 status = pcre_fullinfo(re->re, re->extra, PCRE_INFO_CAPTURECOUNT
118 , &(re->ovec_count));
119 re->ovector = g_new0(int, 3 *(re->ovec_count + 1));
120 return re;
124 static void match_substr_free(MatchSubStr *ms)
126 if (ms)
127 g_free(ms);
131 void
132 match_info_free (MatchInfo *mi)
134 if (mi)
136 if (mi->subs)
138 GList *tmp;
139 for (tmp = mi->subs; tmp; tmp = g_list_next(tmp))
140 match_substr_free((MatchSubStr *) tmp->data);
141 g_list_free(mi->subs);
143 g_free(mi);
148 void
149 file_buffer_free (FileBuffer *fb)
151 if (fb)
153 if (fb->path)
154 g_free(fb->path);
155 if (fb->buf)
156 g_free(fb->buf);
157 if (fb->lines)
158 g_list_free(fb->lines);
159 g_free(fb);
163 /* Create a file buffer structure from a TextEditor structure */
164 FileBuffer *
165 file_buffer_new_from_te (IAnjutaEditor *te)
167 FileBuffer *fb;
168 gchar* uri;
170 g_return_val_if_fail(te, NULL);
171 fb = g_new0(FileBuffer, 1);
172 fb->type = FB_EDITOR;
173 fb->te = te;
175 uri = ianjuta_file_get_uri(IANJUTA_FILE(te), NULL);
176 if (uri)
178 fb->path = tm_get_real_path(uri);
179 g_free (uri);
181 fb->len = ianjuta_editor_get_length(te, NULL);
182 fb->buf = ianjuta_editor_get_text(fb->te, 0, fb->len, NULL);
183 fb->pos = ianjuta_editor_get_position(fb->te, NULL);
184 fb->line = ianjuta_editor_get_lineno(fb->te, NULL);
186 return fb;
189 FileBuffer *
190 file_buffer_new_from_path (const char *path, const char *buf, int len, int pos)
192 FileBuffer *fb;
193 IAnjutaEditor *te;
194 IAnjutaDocument* doc;
195 char *real_path;
196 int i;
197 int lineno;
199 g_return_val_if_fail(path, NULL);
200 real_path = tm_get_real_path(path);
202 /* There might be an already open TextEditor with this path */
203 doc = ianjuta_document_manager_find_document_with_path (sr->docman,
204 real_path, NULL);
205 if (doc && IANJUTA_IS_EDITOR (doc))
207 te = IANJUTA_EDITOR (doc);
208 g_free(real_path);
209 return file_buffer_new_from_te(te);
211 fb = g_new0(FileBuffer, 1);
212 fb->type = FB_FILE;
213 fb->path = real_path;
214 fb->name = strrchr(path, '/');
215 if (fb->name)
216 ++ fb->name;
217 else
218 fb->name = fb->path;
219 if (buf && len > 0)
221 fb->buf = g_new(char, len + 1);
222 memcpy(fb->buf, buf, len);
223 fb->buf[len] = '\0';
224 fb->len = len;
226 else
228 struct stat s;
230 if ((0 == stat(fb->path, &s)) && (S_ISREG(s.st_mode)))
232 if ((fb->len = s.st_size) < 0) return NULL;
233 fb->buf = g_new(char, s.st_size + 1);
235 int total_bytes = 0, bytes_read, fd;
236 if (0 > (fd = open(fb->path, O_RDONLY)))
238 perror(fb->path);
239 file_buffer_free(fb);
240 return NULL;
242 while (total_bytes < s.st_size)
244 if (0 > (bytes_read = read(fd, fb->buf + total_bytes
245 , s.st_size - total_bytes)))
247 perror(fb->path);
248 close(fd);
249 file_buffer_free(fb);
250 return NULL;
252 total_bytes += bytes_read;
254 close(fd);
255 fb->buf[fb->len] = '\0';
259 if (pos <= 0 || pos > fb->len)
261 fb->pos = 0;
262 fb->line = 0;
264 else
266 fb->pos = pos;
267 fb->line = 0;
269 /* First line starts at column 0 */
270 fb->lines = g_list_prepend(fb->lines, GINT_TO_POINTER(0));
271 lineno = 0;
272 for (i=0; i < fb->len; ++i)
274 if ('\n' == fb->buf[i] && '\0' != fb->buf[i+1])
276 fb->lines = g_list_prepend(fb->lines, GINT_TO_POINTER(i + 1));
277 if (0 == fb->line && fb->pos > i)
278 fb->line = lineno;
279 ++ lineno;
282 fb->lines = g_list_reverse(fb->lines);
283 return fb;
286 static long
287 file_buffer_line_from_pos(FileBuffer *fb, int pos)
289 GList *tmp;
290 int lineno = -1;
291 g_return_val_if_fail(fb && pos >= 0, 1);
292 if (FB_FILE == fb->type)
294 for (tmp = fb->lines; tmp; tmp = g_list_next(tmp))
296 if (pos < GPOINTER_TO_INT(tmp->data))
297 return lineno;
298 ++ lineno;
300 return lineno;
302 else if (FB_EDITOR == fb->type)
304 return ianjuta_editor_get_line_from_position(fb->te, pos, NULL);
306 else
307 return -1;
310 gchar *
311 file_match_line_from_pos(FileBuffer *fb, int pos)
313 gint length=1;
314 gint i;
315 g_return_val_if_fail(fb && pos >= 0, NULL);
317 for (i= pos+1; ((fb->buf[i] != '\n') && (fb->buf[i] != '\0')); i++, length++);
318 for (i= pos-1; (fb->buf[i] != '\n') && (i >= 0); i--, length++);
320 return g_strndup (fb->buf + i + 1, length);
323 /* Generate a list of files to search in. Call with start = TRUE and
324 ** top_dir = sf->top_dir. This is used when the search range is specified as
325 SR_FILES */
326 static GList *
327 create_search_files_list(SearchFiles *sf, const char *top_dir)
329 TMFileEntry *entry;
330 GList *files;
332 g_return_val_if_fail(sf && top_dir, NULL);
333 entry = tm_file_entry_new(top_dir, NULL, sf->recurse, sf->match_files
334 , sf->ignore_files, sf->match_dirs, sf->ignore_dirs
335 , sf->ignore_hidden_files, sf->ignore_hidden_dirs);
336 if (!entry)
337 return NULL;
338 files = tm_file_entry_list(entry, NULL);
339 tm_file_entry_free(entry);
340 return files;
343 /* Get a list of all project files */
344 static GList *
345 get_project_file_list(void)
347 GList* list = NULL;
348 GList *files = NULL;
349 gchar *project_root_uri = NULL;
351 anjuta_shell_get (ANJUTA_PLUGIN(sr->docman)->shell,
352 "project_root_uri", G_TYPE_STRING,
353 &project_root_uri, NULL);
355 if (project_root_uri)
357 IAnjutaProjectManager* prjman;
358 prjman = anjuta_shell_get_interface(ANJUTA_PLUGIN(sr->docman)->shell,
359 IAnjutaProjectManager , NULL);
361 list = ianjuta_project_manager_get_elements (prjman,
362 IANJUTA_PROJECT_MANAGER_SOURCE,
363 NULL);
364 if (list)
366 const gchar *uri;
367 GList *node;
368 node = list;
370 while (node)
372 gchar *file_path;
374 uri = (const gchar *)node->data;
375 file_path = gnome_vfs_get_local_path_from_uri (uri);
376 if (file_path)
377 files = g_list_prepend (files, file_path);
378 node = g_list_next (node);
380 files = g_list_reverse (files);
381 g_list_free(list);
384 g_free (project_root_uri);
385 return files;
389 static gboolean
390 isawordchar (int c)
392 return (isalnum(c) || '_' == c);
395 static gboolean
396 extra_match (FileBuffer *fb, SearchExpression *s, gint match_len)
398 gchar b, e;
400 b = fb->buf[fb->pos-1];
401 e = fb->buf[fb->pos+match_len];
403 if (s->whole_line)
404 if ((fb->pos == 0 || b == '\n' || b == '\r') &&
405 (e == '\0' || e == '\n' || e == '\r'))
406 return TRUE;
407 else
408 return FALSE;
409 else if (s->whole_word)
410 if ((fb->pos ==0 || !isawordchar(b)) &&
411 (e=='\0' || !isawordchar(e)))
412 return TRUE;
413 else
414 return FALSE;
415 else if (s->word_start)
416 if (fb->pos ==0 || !isawordchar(b))
417 return TRUE;
418 else
419 return FALSE;
420 else
421 return TRUE;
424 /* Returns the next match in the passed buffer. The search expression should
425 be pre-compiled. The returned pointer should be freed with match_info_free()
426 when no longer required. */
427 MatchInfo *
428 get_next_match(FileBuffer *fb, SearchDirection direction, SearchExpression *s)
430 MatchInfo *mi = NULL;
432 g_return_val_if_fail(fb && s, NULL);
434 if (s->regex)
436 /* Regular expression match */
437 int options = PCRE_NOTEMPTY;
438 int status;
439 if (NULL == s->re)
441 if (NULL == (s->re = pcre_info_new(s)))
442 return NULL;
444 status = pcre_exec (s->re->re, s->re->extra, fb->buf, fb->len, fb->pos,
445 options, s->re->ovector, 3 * (s->re->ovec_count + 1));
446 if (0 == status)
448 /* ovector too small - this should never happen ! */
449 g_warning("BUG ! ovector found to be too small");
450 return NULL;
452 else if (0 > status && status != PCRE_ERROR_NOMATCH)
454 /* match error - again, this should never happen */
455 g_warning("PCRE Match error");
456 return NULL;
458 else if (PCRE_ERROR_NOMATCH != status)
460 mi = g_new0(MatchInfo, 1);
461 mi->pos = s->re->ovector[0];
462 mi->len = s->re->ovector[1] - s->re->ovector[0];
463 mi->line = file_buffer_line_from_pos(fb, mi->pos);
464 if (status > 1) /* Captured subexpressions */
466 int i;
467 MatchSubStr *ms;
468 for (i=1; i < status; ++i)
470 ms = g_new0(MatchSubStr, 1);
471 ms->start = s->re->ovector[i * 2];
472 ms->len = s->re->ovector[i * 2 + 1] - ms->start;
473 mi->subs = g_list_prepend(mi->subs, ms);
475 mi->subs = g_list_reverse(mi->subs);
477 fb->pos = s->re->ovector[1];
480 else
482 /* Simple string search - this needs to be performance-tuned */
483 gboolean found;
484 gchar lc;
485 gint match_len;
487 match_len = strlen (s->search_str);
488 if (match_len == 0)
489 return NULL;
491 found = FALSE;
492 if (SD_BACKWARD == direction)
494 /* Backward matching. */
495 if (s->ignore_case)
497 /* FIXME support encodings with > 1 byte per char */
498 lc = tolower (s->search_str[0]);
499 for (; fb->pos != -1; --fb->pos)
501 if (lc == tolower(fb->buf[fb->pos]))
503 if (0 == g_strncasecmp(s->search_str, fb->buf + fb->pos,
504 match_len) && extra_match (fb, s, match_len))
506 found = TRUE;
507 break;
512 else
514 for (; fb->pos != -1; --fb->pos)
516 if (s->search_str[0] == fb->buf[fb->pos])
518 if (0 == strncmp(s->search_str, fb->buf + fb->pos,
519 match_len) && extra_match (fb, s, match_len))
521 found = TRUE;
522 break;
528 else
530 /* Forward match */
531 if (s->ignore_case)
533 /* FIXME support encodings with > 1 byte per char */
534 lc = tolower (s->search_str[0]);
535 for (; fb->pos < fb->len; ++fb->pos)
537 if (lc == tolower(fb->buf[fb->pos]))
539 if (0 == g_strncasecmp(s->search_str, fb->buf + fb->pos,
540 match_len) && extra_match (fb, s, match_len))
542 found = TRUE;
543 break;
548 else
550 for (; fb->pos < fb->len; ++fb->pos)
552 if (s->search_str[0] == fb->buf[fb->pos])
554 if (0 == strncmp(s->search_str, fb->buf + fb->pos,
555 match_len) && extra_match (fb, s, match_len))
557 found = TRUE;
558 break;
564 if (found)
566 mi = g_new0 (MatchInfo, 1); //better to abort than silently fail to report match ?
567 // mi = g_try_new0 (MatchInfo, 1);
568 // if (mi)
569 // {
570 mi->pos = fb->pos;
571 mi->len = match_len;
572 mi->line = file_buffer_line_from_pos (fb, fb->pos);
573 // }
574 // else
575 // WARN USER ABOUT MEMORY ERROR
576 if (direction == SD_BACKWARD)
577 fb->pos -= match_len;
578 else
579 fb->pos += match_len;
582 return mi;
585 /* Create list of search entries */
586 GList *
587 create_search_entries (Search *s)
589 GList *entries = NULL;
590 GList *tmp;
591 GList *editors;
592 IAnjutaDocument *doc;
593 SearchEntry *se;
594 gint selstart;
595 gint tmp_pos;
597 switch (s->range.type)
599 case SR_BUFFER:
600 doc = ianjuta_document_manager_get_current_document (sr->docman, NULL);
601 if (doc && IANJUTA_IS_EDITOR (doc))
603 se = g_new0 (SearchEntry, 1);
604 se->type = SE_BUFFER;
605 se->te = IANJUTA_EDITOR (doc);
606 se->direction = s->range.direction;
607 if (SD_BEGINNING == se->direction)
609 se->start_pos = 0;
610 se->end_pos = -1;
611 se->direction = SD_FORWARD;
613 else
615 /* forward-search from after beginning of selection, if any
616 backwards-search from before beginning of selection, if any
617 treat -ve positions except -1 as high +ve */
618 selstart = ianjuta_editor_selection_get_start
619 (IANJUTA_EDITOR_SELECTION (se->te), NULL);
620 if (selstart != -1)
622 if (se->direction == SD_BACKWARD)
624 se->start_pos = (selstart != 0) ?
625 selstart - 1 : selstart;
627 else
628 se->start_pos =
629 (selstart != -2 &&
630 selstart < ianjuta_editor_get_length (IANJUTA_EDITOR (se->te), NULL)) ?
631 selstart + 1 : selstart;
633 else
634 se->start_pos = ianjuta_editor_get_position (se->te, NULL);
636 se->end_pos = -1; /* not actually used when backward searching */
638 entries = g_list_prepend(entries, se);
640 break;
641 case SR_SELECTION:
642 case SR_BLOCK:
643 case SR_FUNCTION:
644 doc = ianjuta_document_manager_get_current_document (sr->docman, NULL);
645 if (doc && IANJUTA_IS_EDITOR (doc))
647 gint selend;
649 se = g_new0 (SearchEntry, 1);
650 se->type = SE_BUFFER;
651 se->te = IANJUTA_EDITOR (doc);
652 se->direction = s->range.direction;
653 if (se->direction == SD_BEGINNING)
654 se->direction = SD_FORWARD;
656 if (s->range.type == SR_SELECTION)
658 selstart = selend = 0; /* warning prevention only */
660 else
662 selstart = selend =
663 ianjuta_editor_selection_get_end (IANJUTA_EDITOR_SELECTION (se->te), NULL);
666 if (s->range.type == SR_BLOCK)
667 ianjuta_editor_selection_select_block(IANJUTA_EDITOR_SELECTION (se->te), NULL);
668 if (s->range.type == SR_FUNCTION)
669 ianjuta_editor_selection_select_function(IANJUTA_EDITOR_SELECTION (se->te), NULL);
670 se->start_pos = ianjuta_editor_selection_get_start(IANJUTA_EDITOR_SELECTION (se->te), NULL);
671 se->end_pos = ianjuta_editor_selection_get_end(IANJUTA_EDITOR_SELECTION (se->te), NULL);
673 if (se->direction == SD_BACKWARD)
675 tmp_pos = se->start_pos;
676 se->start_pos = se->end_pos;
677 se->end_pos = tmp_pos;
679 entries = g_list_prepend (entries, se);
680 if (s->range.type != SR_SELECTION)
682 gboolean backward;
683 backward = (se->direction == SD_BACKWARD);
684 ianjuta_editor_selection_set(IANJUTA_EDITOR_SELECTION (se->te),
685 selstart, selend, backward,
686 NULL);
689 break;
690 case SR_OPEN_BUFFERS:
691 editors = ianjuta_document_manager_get_doc_widgets (sr->docman, NULL);
692 for (tmp = editors; tmp; tmp = g_list_next(tmp))
694 if (IANJUTA_IS_EDITOR (tmp->data))
696 se = g_new0 (SearchEntry, 1);
697 se->type = SE_BUFFER;
698 se->te = IANJUTA_EDITOR (tmp->data);
699 se->direction = SD_FORWARD;
700 se->start_pos = 0;
701 se->end_pos = -1;
702 entries = g_list_prepend(entries, se);
705 entries = g_list_reverse(entries);
706 g_list_free (editors);
707 break;
708 case SR_FILES:
709 case SR_PROJECT:
711 GList *files = NULL;
712 gchar *dir = NULL;
713 gchar *dir_uri = NULL;
715 anjuta_shell_get (ANJUTA_PLUGIN(sr->docman)->shell,
716 "project_root_uri", G_TYPE_STRING,
717 &dir_uri, NULL);
718 // FIXME : Replace Standard UNIX IO functions by gnome-vfs
719 if (dir_uri)
720 dir = gnome_vfs_get_local_path_from_uri(dir_uri);
722 if (!dir)
724 if (SR_PROJECT == s->range.type)
725 s->range.type = SR_FILES;
726 dir = g_get_current_dir();
729 if (SR_FILES == s->range.type)
730 files = create_search_files_list(&(s->range.files), dir);
731 else /* if (SR_PROJECT == s->range.type) */
732 files = get_project_file_list();
734 if (files)
736 for (tmp = files; tmp; tmp = g_list_next(tmp))
738 se = g_new0(SearchEntry, 1);
739 se->type = SE_FILE;
740 se->path = (char *) tmp->data;
741 se->direction = SD_FORWARD;
742 se->type = SE_FILE;
743 se->start_pos = 0;
744 se->end_pos = -1;
745 entries = g_list_prepend(entries, se);
747 g_list_free(files);
748 entries = g_list_reverse(entries);
750 g_free(dir);
751 g_free(dir_uri);
752 break;
755 return entries;
758 gchar *
759 regex_backref (MatchInfo *mi, FileBuffer *fb)
761 #define REGX_BUFSIZE 1024
762 gint i, j, k;
763 gint nb_backref;
764 gint i_backref;
765 gint plen;
766 gint start, len;
767 gint backref[10] [2]; /* backref [0][2] unused */
768 gchar buf [REGX_BUFSIZE + 4]; /* usable space + word-sized space for trailing 0 */
769 GList *tmp;
771 i = 1;
772 /* Extract back references */
773 tmp = mi->subs;
774 while (tmp && i < 10)
776 backref[i] [0] = ((MatchSubStr*)tmp->data)->start;
777 backref[i] [1] = ((MatchSubStr*)tmp->data)->len;
778 tmp= g_list_next(tmp);
779 i++;
781 nb_backref = i;
782 plen = strlen (sr->replace.repl_str);
783 for(i=0, j=0; i < plen && j < REGX_BUFSIZE; i++)
785 if (sr->replace.repl_str[i] == '\\')
787 i++;
788 if (sr->replace.repl_str[i] > '0' && sr->replace.repl_str[i] <= '9')
790 i_backref = sr->replace.repl_str[i] - '0';
791 if (i_backref < nb_backref)
793 start = backref[i_backref] [0];
794 len = backref[i_backref] [1];
795 for (k=0; k < len && j < REGX_BUFSIZE; k++)
796 buf[j++] = fb->buf[start + k];
800 else
801 buf[j++] = sr->replace.repl_str[i];
803 buf[j] = '\0';
805 return g_strdup (buf);
808 #define FREE_FN(fn, v) if (v) { fn(v); v = NULL; }
810 void
811 clear_search_replace_instance(void)
813 g_free (sr->search.expr.search_str);
814 g_free (sr->search.expr.re);
815 FREE_FN(pcre_info_free, sr->search.expr.re);
816 if (SR_FILES == sr->search.range.type)
818 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.match_files);
819 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.ignore_files);
820 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.match_dirs);
821 FREE_FN(anjuta_util_glist_strings_free, sr->search.range.files.ignore_dirs);
823 FREE_FN(anjuta_util_glist_strings_free, sr->search.expr_history);
824 g_free (sr->replace.repl_str);
825 FREE_FN(anjuta_util_glist_strings_free, sr->replace.expr_history);
828 void
829 clear_pcre(void)
831 FREE_FN(pcre_info_free, sr->search.expr.re);
834 SearchReplace *
835 create_search_replace_instance(IAnjutaDocumentManager *docman)
837 if (NULL == sr) /* Create a new SearchReplace instance */
838 sr = g_new0(SearchReplace, 1);
839 else
840 clear_search_replace_instance ();
841 if (docman)
842 sr->docman = docman;
843 return sr;