1 /*
2  * TextFileType.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.common.filetypes;
16 
17 import com.google.gwt.resources.client.ImageResource;
18 
19 import org.rstudio.core.client.FilePosition;
20 import org.rstudio.core.client.UnicodeLetters;
21 import org.rstudio.core.client.command.AppCommand;
22 import org.rstudio.core.client.files.FileSystemItem;
23 import org.rstudio.core.client.regex.Pattern;
24 import org.rstudio.studio.client.application.events.EventBus;
25 import org.rstudio.studio.client.common.filetypes.events.OpenSourceFileEvent;
26 import org.rstudio.studio.client.common.filetypes.model.NavigationMethods;
27 import org.rstudio.studio.client.common.reditor.EditorLanguage;
28 import org.rstudio.studio.client.workbench.commands.Commands;
29 import org.rstudio.studio.client.workbench.views.source.SourceWindowManager;
30 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.CharClassifier;
31 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.spelling.TokenPredicate;
32 
33 import java.util.HashSet;
34 
35 public class TextFileType extends EditableFileType
36 {
TextFileType(String id, String label, EditorLanguage editorLanguage, String defaultExtension, ImageResource defaultIcon, boolean wordWrap, boolean canSourceOnSave, boolean canExecuteCode, boolean canExecuteAllCode, boolean canExecuteToCurrentLine, boolean canPreviewHTML, boolean canKnitToHTML, boolean canCompilePDF, boolean canExecuteChunks, boolean canAutoIndent, boolean canCheckSpelling, boolean canShowScopeTree, boolean canPreviewFromR)37    TextFileType(String id,
38                 String label,
39                 EditorLanguage editorLanguage,
40                 String defaultExtension,
41                 ImageResource defaultIcon,
42                 boolean wordWrap,
43                 boolean canSourceOnSave,
44                 boolean canExecuteCode,
45                 boolean canExecuteAllCode,
46                 boolean canExecuteToCurrentLine,
47                 boolean canPreviewHTML,
48                 boolean canKnitToHTML,
49                 boolean canCompilePDF,
50                 boolean canExecuteChunks,
51                 boolean canAutoIndent,
52                 boolean canCheckSpelling,
53                 boolean canShowScopeTree,
54                 boolean canPreviewFromR)
55    {
56       super(id, label, defaultIcon);
57       editorLanguage_ = editorLanguage;
58       defaultExtension_ = defaultExtension;
59       wordWrap_ = wordWrap;
60       canSourceOnSave_ = canSourceOnSave;
61       canExecuteCode_ = canExecuteCode;
62       canExecuteAllCode_ = canExecuteAllCode;
63       canExecuteToCurrentLine_ = canExecuteToCurrentLine;
64       canKnitToHTML_ = canKnitToHTML;
65       canPreviewHTML_ = canPreviewHTML;
66       canCompilePDF_ = canCompilePDF;
67       canExecuteChunks_ = canExecuteChunks;
68       canAutoIndent_ = canAutoIndent;
69       canCheckSpelling_ = canCheckSpelling;
70       canShowScopeTree_ = canShowScopeTree;
71       canPreviewFromR_ = canPreviewFromR;
72    }
73 
74    @Override
openFile(FileSystemItem file, FilePosition position, int navMethod, EventBus eventBus)75    public void openFile(FileSystemItem file,
76                         FilePosition position,
77                         int navMethod,
78                         EventBus eventBus)
79    {
80       eventBus.fireEvent(new OpenSourceFileEvent(file,
81                                                  position,
82                                                  this,
83                                                  navMethod));
84    }
85 
86    @Override
openFile(FileSystemItem file, EventBus eventBus)87    public void openFile(FileSystemItem file, EventBus eventBus)
88    {
89       openFile(file, null, NavigationMethods.DEFAULT, eventBus);
90    }
91 
getEditorLanguage()92    public EditorLanguage getEditorLanguage()
93    {
94       return editorLanguage_;
95    }
96 
getWordWrap()97    public boolean getWordWrap()
98    {
99       return wordWrap_;
100    }
101 
canSource()102    public boolean canSource()
103    {
104       return canExecuteCode_ && !canExecuteChunks_;
105    }
106 
canSourceWithEcho()107    public boolean canSourceWithEcho()
108    {
109       return canSource();
110    }
111 
canSourceOnSave()112    public boolean canSourceOnSave()
113    {
114       return canSourceOnSave_;
115    }
116 
canExecuteCode()117    public boolean canExecuteCode()
118    {
119       return canExecuteCode_;
120    }
121 
canExecuteAllCode()122    public boolean canExecuteAllCode()
123    {
124       return canExecuteAllCode_;
125    }
126 
canExecuteToCurrentLine()127    public boolean canExecuteToCurrentLine()
128    {
129       return canExecuteToCurrentLine_;
130    }
131 
canKnitToHTML()132    public boolean canKnitToHTML()
133    {
134       return canKnitToHTML_;
135    }
136 
canPreviewHTML()137    public boolean canPreviewHTML()
138    {
139       return canPreviewHTML_;
140    }
141 
canCompilePDF()142    public boolean canCompilePDF()
143    {
144       return canCompilePDF_;
145    }
146 
canCompileNotebook()147    public boolean canCompileNotebook()
148    {
149       return false;
150    }
151 
canAuthorContent()152    public boolean canAuthorContent()
153    {
154       return canKnitToHTML() || canPreviewHTML() || canCompilePDF();
155    }
156 
canExecuteChunks()157    public boolean canExecuteChunks()
158    {
159       return canExecuteChunks_;
160    }
161 
canAutoIndent()162    public boolean canAutoIndent()
163    {
164       return canAutoIndent_;
165    }
166 
canCheckSpelling()167    public boolean canCheckSpelling()
168    {
169       return canCheckSpelling_;
170    }
171 
canShowScopeTree()172    public boolean canShowScopeTree()
173    {
174       return canShowScopeTree_;
175    }
176 
canGoNextPrevSection()177    public boolean canGoNextPrevSection()
178    {
179       return isRmd() || isRpres();
180    }
181 
canPreviewFromR()182    public boolean canPreviewFromR()
183    {
184       return canPreviewFromR_;
185    }
186 
isText()187    public boolean isText()
188    {
189       return FileTypeRegistry.TEXT.getTypeId().equals(getTypeId());
190    }
191 
isR()192    public boolean isR()
193    {
194       return FileTypeRegistry.R.getTypeId().equals(getTypeId());
195    }
196 
isRnw()197    public boolean isRnw()
198    {
199       return FileTypeRegistry.SWEAVE.getTypeId().equals(getTypeId());
200    }
201 
isRd()202    public boolean isRd()
203    {
204       return FileTypeRegistry.RD.getTypeId().equals(getTypeId());
205    }
206 
isJS()207    public boolean isJS()
208    {
209       return FileTypeRegistry.JS.getTypeId().equals(getTypeId());
210    }
211 
isRmd()212    public boolean isRmd()
213    {
214       return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId()) ||
215              isQuartoMarkdown();
216    }
217 
isRhtml()218    public boolean isRhtml()
219    {
220       return FileTypeRegistry.RHTML.getTypeId().equals(getTypeId());
221    }
222 
isRpres()223    public boolean isRpres()
224    {
225       return FileTypeRegistry.RPRESENTATION.getTypeId().equals(getTypeId());
226    }
227 
isSql()228    public boolean isSql()
229    {
230       return FileTypeRegistry.SQL.getTypeId().equals(getTypeId());
231    }
232 
isYaml()233    public boolean isYaml()
234    {
235       return FileTypeRegistry.YAML.getTypeId().equals(getTypeId());
236    }
237 
requiresKnit()238    public boolean requiresKnit()
239    {
240       return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId()) ||
241              FileTypeRegistry.RHTML.getTypeId().equals(getTypeId()) ||
242              FileTypeRegistry.RPRESENTATION.getTypeId().equals(getTypeId());
243    }
244 
isMarkdown()245    public boolean isMarkdown()
246    {
247       return FileTypeRegistry.RMARKDOWN.getTypeId().equals(getTypeId()) ||
248              isQuartoMarkdown() ||
249              FileTypeRegistry.MARKDOWN.getTypeId().equals(getTypeId());
250    }
251 
isQuartoMarkdown()252    public boolean isQuartoMarkdown()
253    {
254       return FileTypeRegistry.QUARTO.getTypeId().equals(getTypeId());
255    }
256 
isPlainMarkdown()257    public boolean isPlainMarkdown()
258    {
259       return FileTypeRegistry.MARKDOWN.getTypeId().equals(getTypeId());
260    }
261 
isRNotebook()262    public boolean isRNotebook()
263    {
264       return FileTypeRegistry.RNOTEBOOK.getTypeId().equals(getTypeId());
265    }
266 
isC()267    public boolean isC()
268    {
269       return EditorLanguage.LANG_CPP.equals(getEditorLanguage());
270    }
271 
isPython()272    public boolean isPython()
273    {
274       return EditorLanguage.LANG_PYTHON.equals(getEditorLanguage());
275    }
276 
isCpp()277    public boolean isCpp()
278    {
279       return false;
280    }
281 
isStan()282    public boolean isStan()
283    {
284       return EditorLanguage.LANG_STAN.equals(getEditorLanguage());
285    }
286 
getPreviewButtonText()287    public String getPreviewButtonText()
288    {
289       return "Preview";
290    }
291 
createPreviewCommand(String file)292    public String createPreviewCommand(String file)
293    {
294       return null;
295    }
296 
isScript()297    public boolean isScript()
298    {
299       return false;
300    }
301 
getScriptInterpreter()302    public String getScriptInterpreter()
303    {
304       return null;
305    }
306 
getSupportedCommands(Commands commands)307    public HashSet<AppCommand> getSupportedCommands(Commands commands)
308    {
309       HashSet<AppCommand> results = new HashSet<>();
310       results.add(commands.saveSourceDoc());
311       results.add(commands.reopenSourceDocWithEncoding());
312       results.add(commands.saveSourceDocAs());
313       results.add(commands.saveSourceDocWithEncoding());
314       results.add(commands.printSourceDoc());
315       results.add(commands.vcsFileLog());
316       results.add(commands.vcsFileDiff());
317       results.add(commands.vcsFileRevert());
318       results.add(commands.vcsViewOnGitHub());
319       results.add(commands.vcsBlameOnGitHub());
320       results.add(commands.goToLine());
321       results.add(commands.expandSelection());
322       results.add(commands.shrinkSelection());
323 
324       if (isJS())
325       {
326          results.add(commands.previewJS());
327       }
328 
329       if (isSql())
330       {
331          results.add(commands.previewSql());
332       }
333 
334       if (isYaml())
335       {
336          results.add(commands.commentUncomment());
337       }
338 
339       if ((canExecuteCode() && !isScript()) || isC())
340       {
341          results.add(commands.reindent());
342          results.add(commands.showDiagnosticsActiveDocument());
343       }
344 
345       if (canExecuteCode() && !isC() && !isScript())
346       {
347          results.add(commands.executeCurrentFunction());
348       }
349 
350       if (canExecuteCode())
351       {
352          results.add(commands.executeCode());
353          results.add(commands.executeCodeWithoutFocus());
354 
355          if (!isScript())
356          {
357             results.add(commands.executeLastCode());
358             results.add(commands.extractFunction());
359             results.add(commands.extractLocalVariable());
360             results.add(commands.commentUncomment());
361             results.add(commands.reflowComment());
362             results.add(commands.reformatCode());
363             results.add(commands.renameInScope());
364             results.add(commands.profileCode());
365             results.add(commands.profileCodeWithoutFocus());
366          }
367       }
368 
369       if (canExecuteAllCode())
370       {
371          results.add(commands.executeAllCode());
372          results.add(commands.sourceActiveDocument());
373          // file types with chunks take the Cmd+Shift+Enter shortcut
374          // for run current chunk
375          if (!canExecuteChunks())
376             results.add(commands.sourceActiveDocumentWithEcho());
377       }
378 
379       if (canExecuteToCurrentLine())
380       {
381          results.add(commands.executeToCurrentLine());
382          results.add(commands.executeFromCurrentLine());
383          results.add(commands.executeCurrentSection());
384          results.add(commands.profileCode());
385       }
386       if (canKnitToHTML())
387       {
388          results.add(commands.editRmdFormatOptions());
389          results.add(commands.knitWithParameters());
390          results.add(commands.clearKnitrCache());
391          results.add(commands.restartRClearOutput());
392          results.add(commands.restartRRunAllChunks());
393          results.add(commands.notebookCollapseAllOutput());
394          results.add(commands.notebookExpandAllOutput());
395          results.add(commands.executeSetupChunk());
396       }
397 
398       if (canKnitToHTML() || canCompileNotebook())
399       {
400          results.add(commands.knitDocument());
401       }
402       if (canPreviewHTML())
403       {
404          results.add(commands.previewHTML());
405       }
406       if (canKnitToHTML() || canPreviewHTML())
407       {
408          results.add(commands.quartoRenderDocument());
409       }
410       if (canCompilePDF())
411       {
412          results.add(commands.compilePDF());
413          results.add(commands.synctexSearch());
414       }
415       if (canCompileNotebook())
416       {
417          results.add(commands.compileNotebook());
418       }
419       if (canExecuteChunks())
420       {
421          results.add(commands.insertChunk());
422          results.add(commands.executePreviousChunks());
423          results.add(commands.executeSubsequentChunks());
424          results.add(commands.executeCurrentChunk());
425          results.add(commands.executeNextChunk());
426          results.add(commands.runSelectionAsJob());
427          results.add(commands.runSelectionAsLauncherJob());
428       }
429       if (isMarkdown())
430       {
431          results.add(commands.toggleRmdVisualMode());
432          results.add(commands.enableProsemirrorDevTools());
433       }
434       if (canCheckSpelling())
435       {
436          results.add(commands.checkSpelling());
437       }
438       if (canShowScopeTree())
439       {
440          results.add(commands.toggleDocumentOutline());
441       }
442 
443       results.add(commands.wordCount());
444       results.add(commands.goToNextSection());
445       results.add(commands.goToPrevSection());
446       results.add(commands.goToNextChunk());
447       results.add(commands.goToPrevChunk());
448       results.add(commands.findReplace());
449       results.add(commands.findNext());
450       results.add(commands.findPrevious());
451       results.add(commands.findFromSelection());
452       results.add(commands.replaceAndFind());
453       results.add(commands.setWorkingDirToActiveDoc());
454       results.add(commands.debugDumpContents());
455       results.add(commands.debugImportDump());
456       results.add(commands.popoutDoc());
457       if (!SourceWindowManager.isMainSourceWindow())
458          results.add(commands.returnDocToMain());
459 
460       if (isR())
461       {
462          results.add(commands.sourceAsLauncherJob());
463          results.add(commands.sourceAsJob());
464          results.add(commands.runSelectionAsJob());
465          results.add(commands.runSelectionAsLauncherJob());
466          results.add(commands.runDocumentFromServerDotR());
467       }
468 
469       results.add(commands.sendToTerminal());
470       results.add(commands.sendFilenameToTerminal());
471       results.add(commands.openNewTerminalAtEditorLocation());
472       results.add(commands.toggleSoftWrapMode());
473 
474       return results;
475    }
476 
getDefaultExtension()477    public String getDefaultExtension()
478    {
479       return defaultExtension_;
480    }
481 
getTokenPredicate()482    public TokenPredicate getTokenPredicate()
483    {
484       return (token, row, column) ->
485       {
486          if (reNospellType_.match(token.getType(), 0) != null) {
487             return false;
488          }
489 
490          return reTextType_.match(token.getType(), 0) != null ||
491             reStringType_.match(token.getType(), 0) != null ||
492             reHeaderType_.match(token.getType(), 0) != null ||
493             reCommentType_.match(token.getType(), 0) != null;
494       };
495    }
496 
497    // default to only returning comments and text, override in subclasses
498    // for more or less specificity
499    public TokenPredicate getSpellCheckTokenPredicate()
500    {
501       return (token, row, column) ->
502       {
503          if (reNospellType_.match(token.getType(), 0) != null) {
504             return false;
505          }
506 
507          return (reCommentType_.match(token.getType(), 0) != null ||
508                  reTextType_.match(token.getType(), 0) != null) &&
509                  reKeywordType_.match(token.getType(), 0) == null &&
510                  reIdentifierType_.match(token.getType(), 0) == null;
511       };
512    }
513 
514    public CharClassifier getCharPredicate()
515    {
516       return new CharClassifier()
517       {
518          @Override
519          public CharClass classify(char c)
520          {
521             if (UnicodeLetters.isLetter(c))
522                return CharClass.Word;
523             else if (c == '\'' || c == '’')
524                return CharClass.Boundary;
525             else
526                return CharClass.NonWord;
527          }
528       };
529    }
530 
531    /**
532     * Returns a regex pattern that will match against the beginning of
533     * Rnw lines, right up to the index where options begin.
534     */
535    public Pattern getRnwStartPatternBegin()
536    {
537       return null;
538    }
539 
540    public Pattern getRnwStartPatternEnd()
541    {
542       return null;
543    }
544 
545    private final EditorLanguage editorLanguage_;
546    private final boolean wordWrap_;
547    private final boolean canSourceOnSave_;
548    private final boolean canExecuteCode_;
549    private final boolean canExecuteAllCode_;
550    private final boolean canExecuteToCurrentLine_;
551    private final boolean canPreviewHTML_;
552    private final boolean canKnitToHTML_;
553    private final boolean canCompilePDF_;
554    private final boolean canExecuteChunks_;
555    private final boolean canAutoIndent_;
556    private final boolean canCheckSpelling_;
557    private final boolean canShowScopeTree_;
558    private final boolean canPreviewFromR_;
559    private final String defaultExtension_;
560 
561    protected static Pattern reTextType_ = Pattern.create("\\btext\\b");
562    protected static Pattern reStringType_ = Pattern.create("\\bstring\\b");
563    protected static Pattern reHeaderType_ = Pattern.create("\\bheading\\b");
564    protected static Pattern reNospellType_ = Pattern.create("\\bnospell\\b");
565    protected static Pattern reCommentType_ = Pattern.create("\\bcomment\\b");
566    protected static Pattern reKeywordType_ = Pattern.create("\\bkeyword\\b");
567    protected static Pattern reIdentifierType_ = Pattern.create("\\bidentifier\\b");
568 }
569