Rhelper=function(topic) { ## Window and containers window.main=gtkWindow("toplevel",show=FALSE); window.main$setTitle("Rhelper"); window.main$setDefaultSize(800,600); hbox.main=gtkHBoxNew(homogeneous=FALSE); vbox.left=gtkVBoxNew(homogeneous=FALSE); sw1.main=gtkScrolledWindowNew(show=TRUE); sw2.main=gtkScrolledWindowNew(show=TRUE); sw1.main$setPolicy(GtkPolicyType["automatic"],GtkPolicyType["automatic"]); sw2.main$setPolicy(GtkPolicyType["automatic"],GtkPolicyType["automatic"]); sw1.main$setSizeRequest(200,-1); ## Font style font.style=pangoFontDescriptionNew(); #font.style$setSize(10); font.style$setFamily("sans"); ## ComboBox combo.label=gtkLabelNew("Select package:"); gtkMiscSetAlignment(combo.label,0,0.5); gtkWidgetModifyFont(combo.label,font.style); combo.label$setSizeRequest(-1,25); combo.main=gtkComboBoxNewText(); pkgs=.packages(TRUE); for(pkg in pkgs) { gtkComboBoxAppendText(combo.main,pkg); } ## Filter filter.label=gtkLabelNew("Filter:"); gtkMiscSetAlignment(filter.label,0,0.5); gtkWidgetModifyFont(filter.label,font.style); filter.label$setSizeRequest(-1,25); filter.main=gtkEntryNew(); ## Listbox model.main=gtkListStoreNew("gchararray"); listbox.main=gtkTreeViewNewWithModel(model.main); renderer.main=gtkCellRendererTextNew(); column.main=gtkTreeViewColumnNewWithAttributes("",renderer.main,text=0); column.header=gtkLabelNew(); gtkWidgetModifyFont(column.header,font.style); column.header$setSizeRequest(-1,25); column.main$setWidget(column.header); listbox.main$appendColumn(column.main); selection.main=gtkTreeViewGetSelection(listbox.main); selection.main$setMode(GtkSelectionMode["single"]); setListbox=function(topic,widget,pattern=NULL) { model=widget$getModel(); column=widget$getColumn(0); selection=widget$getSelection(); helpfile=as.character(help(topic))[1]; pkgname=basename(dirname(dirname(helpfile))); indexfile=file.path(dirname(helpfile),"AnIndex"); fun.name=readLines(indexfile); fun.name=gsub("\t.*","",fun.name); reload=(pkgname!=gsub("Package: ","",column$getWidget()$getText())); if(!is.null(pattern)) { fun.name=grep(pattern,fun.name,value=TRUE,fixed=TRUE); if(!length(fun.name)) { model$clear(); return(FALSE); } if(!(topic %in% fun.name)) topic=fun.name[1]; reload=TRUE; } if(reload) { model$clear(); for(fname in fun.name) { .RGtkCall("S_gtk_list_store_set_value",model, gtkListStoreAppend(model)$iter, 0L,fname,PACKAGE="RGtk2"); } column$getWidget()$setText(paste("Package:",pkgname)); } index=which(fun.name==topic)[1]-1; list.path=gtkTreePathNewFromIndices(index); selection$selectPath(list.path); widget$scrollToCell(list.path,NULL,FALSE,NULL,NULL); } ## TextView buffer.main=gtkTextBufferNew(); textview.main=gtkTextView(buffer=buffer.main,show=TRUE); textview.main$setEditable(FALSE); textview.main$setWrapMode(GtkWrapMode["word"]); textview.main$setLeftMargin(10); textview.main$setRightMargin(10); ## Tags tag.title=buffer.main$createTag("title", family="serif",scale=PANGO_SCALE_XX_LARGE, weight=PangoWeight["bold"],justification="center", pixels_above_lines=20,pixels_below_lines=20); tag.section=buffer.main$createTag("section", family="serif",scale=PANGO_SCALE_X_LARGE, weight=PangoWeight["bold"],pixels_above_lines=15, pixels_below_lines=12); tag.text=buffer.main$createTag("text",scale=PANGO_SCALE_LARGE, family="sans",pixels_above_lines=8,pixels_below_lines=8); tag.code=buffer.main$createTag("code",foreground="#666666", scale=PANGO_SCALE_LARGE,weight=PangoWeight["bold"], family="monospace",pixels_above_lines=8, pixels_below_lines=8); tag.pre=buffer.main$createTag("pre",scale=PANGO_SCALE_LARGE, family="monospace"); tag.emph=buffer.main$createTag("emph",style="italic", scale=PANGO_SCALE_LARGE,left_margin=50, pixels_above_lines=8,pixels_below_lines=8); tag.bold=buffer.main$createTag("bold",family="sans",scale=PANGO_SCALE_LARGE, weight=PangoWeight["bold"],pixels_above_lines=8, pixels_below_lines=8); tag.arg=buffer.main$createTag("arg",foreground="#666666", scale=PANGO_SCALE_LARGE,weight=PangoWeight["bold"], family="monospace"); tag.discrip=buffer.main$createTag("discrip",left_margin=50); tags.available=c("title","section","text","code","pre","emph", "bold","arg","discrip","link"); ## Rd parser parser=function(topic) { helpfile=as.character(help(topic))[1]; pkgname=basename(dirname(dirname(helpfile))); docname=basename(helpfile); RdDB=file.path(dirname(helpfile),pkgname); Rdobj=tools:::fetchRdDB(RdDB,docname); doc=as.character(Rdobj); # symbols src=c("\\R","\\dots","\\ldots","\\sqrt","\\ge","\\le", "\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta", "\\eta","\\theta","\\iota","\\kappa","\\lambda","\\mu","\\nv", "\\xi","\\omicron","\\pi","\\rho","\\sigma","\\tau","\\upsilon", "\\phi","\\chi","\\psi","\\omega","\\Alpha","\\Beta","\\Gamma", "\\Delta","\\Epsilon","\\Zeta","\\Eta","\\Theta","\\Iota", "\\Kappa","\\Lambda","\\Mu","\\Nv","\\Xi","\\Omicron","\\Pi", "\\Rho","\\Sigma","\\Tau","\\Upsilon","\\Phi","\\Chi","\\Psi", "\\Omega"); target=c("R","...","...","√","≥","≤","α","β","γ","δ","ε","ζ","η","θ", "ι","κ","λ","μ","ν","ξ","ο","π","ρ","σ","τ","υ","φ","χ","ψ","ω", "Α","Β","Γ","Δ","Ε","Ζ","Η","Θ","Ι","Κ","Λ","Μ","Ν","Ξ","Ο","Π", "Ρ","Σ","Τ","Υ","Φ","Χ","Ψ","Ω"); for(i in 1:length(src)) { doc=gsub(src[i],target[i],doc,fixed=TRUE); } # Remove null string. doc=doc[doc!=""]; # Remove the NL in the beginning and at the end of a section. left=(1:length(doc))[doc=="{"]; right=(1:length(doc))[doc=="}"]; doc[left+1]=gsub("^\n","",doc[left+1]); doc[right-1]=gsub("\n$","",doc[right-1]); # attributes of tags square.br=(1:length(doc))[doc=="["]; if(length(square.br)) { for(index in square.br) { if(grepl("^\\\\",doc[index-1])) { doc[index-1]=paste(doc[index-1],doc[index+1],sep=""); doc[(index):(index+2)]=""; } } } # brace brace.pair=function(doc) { left=(1:length(doc))[doc=="{"]; right=(1:length(doc))[doc=="}"]; s=rbind(cbind(left,1),cbind(right,-1)); s=s[order(s[,1]),]; n=dim(s)[1]; left.pair=sapply(left,function(x){ i=which(s[,1]==x)[1]; index=which(cumsum(s[i:n,2])==0)[1]+i-1; return(s[index,1]); }); return(cbind(left,left.pair)); } # Add prefixes and suffixes to the content in {}. doc.add=as.list(doc); doc.add[doc=="{"]="PrEfIx"; doc.add[doc!="{"]=list(NULL); doc=mapply(function(x,y) c(x,y),doc,doc.add,SIMPLIFY=FALSE,USE.NAMES=FALSE); doc.add[doc=="}"]="SuFfIx"; doc.add[doc!="}"]=list(NULL); doc=mapply(function(x,y) c(x,y),doc.add,doc,SIMPLIFY=FALSE,USE.NAMES=FALSE); doc=unlist(doc); # selector selector=function(tag,doc,brace.pairs) { tag.name=paste("^\\\\",tag,sep=""); header=grep(tag.name,doc); if(!length(header)) return(NULL); n=length(doc); left.all=(1:n)[doc=="{"]; header0=intersect(header,left.all-1); if(!length(header0)) return(list("header"=header)); header=header0; left1=header+1; right1=brace.pairs[match(left1,brace.pairs[,1]),2]; prefix1=left1+1; suffix1=right1-1; content1=unlist(mapply(function(x,y) x:y,prefix1+1,suffix1-1),use.names=FALSE) left2=intersect(left.all,right1+1); if(!length(left2)) return(list("header"=header,"left1"=left1, "prefix1"=prefix1,"content1"=content1, "suffix1"=suffix1,"right1"=right1)); right2=brace.pairs[match(left2,brace.pairs[,1]),2]; prefix2=left2+1; suffix2=right2-1; content2=unlist(mapply(function(x,y) x:y,prefix2+1,suffix2-1),use.names=FALSE) return(list("header"=header,"left1"=left1,"prefix1"=prefix1, "content1"=content1,"suffix1"=suffix1, "right1"=right1,"left2"=left2,"prefix2"=prefix2, "content2"=content2,"suffix2"=suffix2,"right2"=right2)); } doc=doc[doc!=""]; brace.pairs=brace.pair(doc); # Find the space to be preserved. space.usage=selector("usage",doc,brace.pairs)$content1; space.examples=selector("examples",doc,brace.pairs)$content1; space.preformatted=selector("preformatted",doc,brace.pairs)$content1; space.pre=c(space.usage,space.examples,space.preformatted); doc[space.pre]=paste("SpAcEpReSeRvEd",doc[space.pre],sep=""); doc=gsub("^ +","",doc); doc=gsub("SpAcEpReSeRvEd","",doc); # Find the NL to be preserved. doc=gsub("^ +\n$","\n",doc); NL.end=grep(".+\n$",doc); NL.arg=selector("arguments",doc,brace.pairs)$content1; NL.item=selector("item$",doc,brace.pairs); NL.item=c(NL.item$content1,NL.item$content2); NL.describe=selector("describe",doc,brace.pairs)$content1; NL.single=(1:length(doc))[doc=="\n"]; NL.pre1=intersect(c(NL.end+1,NL.describe,setdiff(NL.arg,NL.item)),NL.single); NL.pre2=space.usage; NL.pre3=space.examples; NL.pre4=space.preformatted; NL.pre=unique(c(NL.pre1,NL.pre2,NL.pre3,NL.pre4)); doc[NL.pre]=gsub("\n$","NlPrEsErVeD",doc[NL.pre]); # Convert unproteced NL to space. doc=gsub("\n$"," ",doc); doc=gsub("NlPrEsErVeD","\n",doc); # some replacements doc[doc=="\\special"]=""; doc[doc=="\\itemize"]="\\enumerate"; content=selector("sQuote",doc,brace.pairs); doc[c(content$prefix1,content$suffix1)]="\'"; doc[c(content$header,content$left1,content$right1)]=""; content=selector("dQuote",doc,brace.pairs); doc[c(content$prefix1,content$suffix1)]="\""; doc[c(content$header,content$left1,content$right1)]=""; # Delete tags that will not be displayed. tags.delete=c("name","alias","concept","keyword","dontshow"); for(tag in tags.delete) { content=selector(tag,doc,brace.pairs); doc[unlist(content,use.names=FALSE)]=""; } # about the "section" tag content=selector("section",doc,brace.pairs); doc[content$header]=paste("\\section: ",doc[content$content1],sep=""); doc[c(content$left1,content$prefix1,content$content1,content$suffix1, content$right1)]=""; # about the "method" tag content=selector("method",doc,brace.pairs); fun=doc[content$content1]; doc[content$content1]=paste("## S3 method for class \'",doc[content$content2],"\'\n",sep=""); doc[content$content2]=fun; doc[c(content$header,content$left1,content$right1, content$prefix1,content$suffix1)]=""; # about the "describe" tag content=selector("describe",doc,brace.pairs); doc[content$content1]=gsub("\\\\code","",doc[content$content1]); doc[c(content$header,content$left1,content$right1,content$suffix1)]=""; doc[content$prefix1]="\n"; # equations content=selector("d?eqn",doc,brace.pairs); has2parts=match(content$left2-1,content$right1); doc[c(content$left1[has2parts],content$prefix1[has2parts], content$content1[has2parts],content$suffix1[has2parts], content$right1[has2parts])]=""; # deqn doc=doc[doc!=""]; brace.pairs=brace.pair(doc); content=selector("deqn",doc,brace.pairs); doc[c(content$prefix1,content$suffix1)]="\n"; doc[doc=="\\eqn"|doc=="\\deqn"]="\\emph"; # Add NL to the head and tail of sections. doc[c(1:3,6)]=""; doc[5]="\n"; names(doc)[4]="title"; doc=doc[doc!=""]; brace.pairs=brace.pair(doc); sections=c("description","usage","arguments","details", "value","references","note","author","seealso","examples", "source","format"); sections.name=c("Description","Usage","Arguments","Details", "Value","References","Note","Author(s)","See Also","Examples", "Source","Format"); content=selector("usage",doc,brace.pairs); names(doc)[content$content1]="pre"; content=selector("examples",doc,brace.pairs); names(doc)[content$content1]="pre"; for(i in 1:length(sections)) { content=selector(sections[i],doc,brace.pairs); doc[c(content$prefix1,content$suffix1)]="\n"; doc[c(content$left1,content$right1)]=""; doc[content$header]=sections.name[i]; names(doc)[content$header]="section"; } content=selector("section",doc,brace.pairs); doc[c(content$prefix1,content$suffix1)]="\n"; doc[c(content$left1,content$right1)]=""; doc[content$header]=gsub("\\\\section: ","",doc[content$header]); names(doc)[content$header]="section"; content=selector("enumerate",doc,brace.pairs); doc[content$suffix1]="\n"; content=selector("preformatted",doc,brace.pairs); doc[content$content1]=gsub("^ +$","",doc[content$content1]); doc[content$right1+1]=gsub("^ +$","",doc[content$right1+1]); doc[c(content$prefix1,content$suffix1)]="\n"; # item content=selector("item",doc,brace.pairs); item.all=grep("^\\\\item",doc); item.nopar=setdiff(item.all,content$header); doc[item.nopar]="\n●"; names(doc)[content$content1]="arg"; doc[content$header-1]=gsub("^ +$","",doc[content$header-1]); doc[c(content$header,content$left1,content$right1)]=""; names(doc)[content$content2]="discrip"; doc[c(content$left2,content$right2)]=""; doc[content$prefix2]="\n"; # dontrun content=selector("dontrun",doc,brace.pairs); doc[content$prefix1]="## Not run:\n"; doc[content$suffix1]="\n## End(Not run)\n"; doc[c(content$header,content$left1,content$right1)]=""; # Replace tags. # Fix: kbd, href, dfn, cite, acronym doc[doc=="\\env" | doc=="\\command" | doc=="\\preformatted" | doc=="\\option"]="\\pre"; doc[doc=="\\strong" | doc=="\\pkg" | doc=="\\email"]="\\bold"; doc[doc=="\\samp" | doc=="\\file" | doc=="\\verb"]="\\code"; doc[doc=="\\enumerate"]="\\discrip"; doc[doc=="\\var" | doc=="\\kbd"]="\\emph"; doc[doc=="\\url"]="\\link"; doc[doc=="\\donttest"]=""; # Set the format of texts by tags. doc=doc[doc!=""]; brace.pairs=brace.pair(doc); # discrip -> code -> emph -> link The order is important. tags.format=c("discrip","pre","bold","code","emph","link"); for(tag in tags.format) { content=selector(tag,doc,brace.pairs); names(doc)[content$content1]=paste(names(doc)[content$content1],tag,sep=".") doc[c(content$header,content$left1,content$right1,content$prefix1,content$suffix1)]=""; } doc[doc=="{" | doc=="}" | doc=="PrEfIx" | doc=="SuFfIx"]=""; doc=doc[doc!=""]; names(doc)[is.na(names(doc))]="text"; names(doc)=gsub("NA\\.","",names(doc)); names(doc)=gsub("code\\.link","link",names(doc)); names(doc)[names(doc)=="discrip"]="discrip.text"; return(doc); } ## Write text writeText=function(topic,buf) { doc=parser(topic); buf$setText(""); iter.t=buf$getIterAtOffset(0)$iter; for(i in 1:length(doc)) { tags=unlist(strsplit(names(doc)[i],"\\.")); tags=intersect(tags,tags.available); if("link" %in% tags) { tag.link=buf$createTag(NULL,foreground="blue",underline="single", scale=PANGO_SCALE_LARGE,weight=PangoWeight["bold"], family="monospace",pixels_above_lines=8, pixels_below_lines=8); tag.link$setData("link",doc[i]); buf$insertWithTags(iter.t,doc[i],tag.link); }else{ .RGtkCall("S_gtk_text_buffer_insert_with_tags_by_name", buf,iter.t,doc[i],tags); } } } ## Signal combo_changed=function(widget,param) { pkg=widget$getActiveText(); if(!require(pkg,character.only=TRUE)) return(FALSE); topic=ls(paste("package:",pkg,sep=""))[1]; setListbox(topic,param$listbox); return(FALSE); } selection_changed=function(widget,param) { if(!widget$getSelected()$retval) return(FALSE); iter=widget$getSelected()$iter; topic=param$model$getValue(iter,0)$value; writeText(topic,param$buffer); return(FALSE); } key_pressed=function(widget,event,param) { if(event$keyval!=65293) return(FALSE); pkg=param$listbox$getColumn(0)$getWidget()$getText(); pkg=gsub("Package: ","",pkg); topic=ls(paste("package:",pkg,sep=""))[1]; pattern=widget$getText(); setListbox(topic,param$listbox,pattern); return(FALSE); } link_clicked=function(widget,event,param) { if(event$type!=GdkEventType['button-release']-1) return(FALSE); if(event$button!=1) return(FALSE); buffer=widget$getBuffer(); iter.bounds=buffer$getSelectionBounds(); if(iter.bounds$start$getOffset()!=iter.bounds$end$getOffset()) return(FALSE); buffer.coords=widget$windowToBufferCoords(GtkTextWindowType["widget"],event$x,event$y); iter=widget$getIterAtLocation(buffer.coords$buffer.x,buffer.coords$buffer.y)$iter; if(!length(iter$getTags())) return(FALSE); link=iter$getTags()[[1]]$getData("link"); if(!is.null(link)) { if(!grepl("^http",link)) { setListbox(link,param$listbox); }else browseURL(link); } return(FALSE); } gSignalConnect(combo.main,"changed",combo_changed,list(listbox=listbox.main)); gSignalConnect(selection.main,"changed",selection_changed,list(model=model.main,buffer=buffer.main)); gSignalConnect(filter.main,"key-press-event",key_pressed,list(listbox=listbox.main)); gSignalConnect(textview.main,"event-after",link_clicked,list(listbox=listbox.main)); ## Initialize setListbox(topic,listbox.main); ## Show window window.main$add(hbox.main); hbox.main$packStart(vbox.left,FALSE,FALSE,2); hbox.main$packStart(sw2.main); vbox.left$packStart(combo.label,FALSE,FALSE); vbox.left$packStart(combo.main,FALSE,FALSE); vbox.left$packStart(filter.label,FALSE,FALSE); vbox.left$packStart(filter.main,FALSE,FALSE); vbox.left$packStart(sw1.main); sw1.main$add(listbox.main); sw2.main$add(textview.main); window.main$show(); }