1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #pragma once
27 
28 #include "jucer_DocumentEditorComponent.h"
29 
30 //==============================================================================
31 class SourceCodeDocument  : public OpenDocumentManager::Document
32 {
33 public:
34     SourceCodeDocument (Project*, const File&);
35 
loadedOk()36     bool loadedOk() const override                           { return true; }
isForFile(const File & file)37     bool isForFile (const File& file) const override         { return getFile() == file; }
isForNode(const ValueTree &)38     bool isForNode (const ValueTree&) const override         { return false; }
refersToProject(Project & p)39     bool refersToProject (Project& p) const override         { return project == &p; }
getProject()40     Project* getProject() const override                     { return project; }
getName()41     String getName() const override                          { return getFile().getFileName(); }
getType()42     String getType() const override                          { return getFile().getFileExtension() + " file"; }
getFile()43     File getFile() const override                            { return modDetector.getFile(); }
needsSaving()44     bool needsSaving() const override                        { return codeDoc != nullptr && codeDoc->hasChangedSinceSavePoint(); }
hasFileBeenModifiedExternally()45     bool hasFileBeenModifiedExternally() override            { return modDetector.hasBeenModified(); }
fileHasBeenRenamed(const File & newFile)46     void fileHasBeenRenamed (const File& newFile) override   { modDetector.fileHasBeenRenamed (newFile); }
getState()47     String getState() const override                         { return lastState != nullptr ? lastState->toString() : String(); }
restoreState(const String & state)48     void restoreState (const String& state) override         { lastState.reset (new CodeEditorComponent::State (state)); }
49 
getCounterpartFile()50     File getCounterpartFile() const override
51     {
52         auto file = getFile();
53 
54         if (file.hasFileExtension (sourceFileExtensions))
55         {
56             static const char* extensions[] = { "h", "hpp", "hxx", "hh", nullptr };
57             return findCounterpart (file, extensions);
58         }
59 
60         if (file.hasFileExtension (headerFileExtensions))
61         {
62             static const char* extensions[] = { "cpp", "mm", "cc", "cxx", "c", "m", nullptr };
63             return findCounterpart (file, extensions);
64         }
65 
66         return {};
67     }
68 
findCounterpart(const File & file,const char ** extensions)69     static File findCounterpart (const File& file, const char** extensions)
70     {
71         while (*extensions != nullptr)
72         {
73             auto f = file.withFileExtension (*extensions++);
74 
75             if (f.existsAsFile())
76                 return f;
77         }
78 
79         return {};
80     }
81 
82     void reloadFromFile() override;
83     bool save() override;
84     bool saveAs() override;
85 
86     Component* createEditor() override;
createViewer()87     Component* createViewer() override       { return createEditor(); }
88 
89     void updateLastState (CodeEditorComponent&);
90     void applyLastState (CodeEditorComponent&) const;
91 
92     CodeDocument& getCodeDocument();
93 
94     //==============================================================================
95     struct Type  : public OpenDocumentManager::DocumentType
96     {
canOpenFileType97         bool canOpenFile (const File& file) override
98         {
99             if (file.hasFileExtension (sourceOrHeaderFileExtensions)
100                  || file.hasFileExtension ("txt;inc;tcc;xml;plist;rtf;html;htm;php;py;rb;cs"))
101                 return true;
102 
103             MemoryBlock mb;
104             if (file.loadFileAsData (mb)
105                  && seemsToBeText (static_cast<const char*> (mb.getData()), (int) mb.getSize())
106                  && ! file.hasFileExtension ("svg"))
107                 return true;
108 
109             return false;
110         }
111 
seemsToBeTextType112         static bool seemsToBeText (const char* const chars, const int num) noexcept
113         {
114             for (int i = 0; i < num; ++i)
115             {
116                 const char c = chars[i];
117                 if ((c < 32 && c != '\t' && c != '\r' && c != '\n') || chars[i] > 126)
118                     return false;
119             }
120 
121             return true;
122         }
123 
openFileType124         Document* openFile (Project* p, const File& file) override   { return new SourceCodeDocument (p, file); }
125     };
126 
127 protected:
128     FileModificationDetector modDetector;
129     std::unique_ptr<CodeDocument> codeDoc;
130     Project* project;
131 
132     std::unique_ptr<CodeEditorComponent::State> lastState;
133 
134     void reloadInternal();
135 };
136 
137 class GenericCodeEditorComponent;
138 
139 //==============================================================================
140 class SourceCodeEditor  : public DocumentEditorComponent,
141                           private ValueTree::Listener,
142                           private CodeDocument::Listener
143 {
144 public:
145     SourceCodeEditor (OpenDocumentManager::Document*, CodeDocument&);
146     SourceCodeEditor (OpenDocumentManager::Document*, GenericCodeEditorComponent*);
147     ~SourceCodeEditor() override;
148 
149     void scrollToKeepRangeOnScreen (Range<int> range);
150     void highlight (Range<int> range, bool cursorAtStart);
151 
152     std::unique_ptr<GenericCodeEditorComponent> editor;
153 
154 private:
155     void resized() override;
156     void lookAndFeelChanged() override;
157 
158     void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
159     void valueTreeChildAdded (ValueTree&, ValueTree&) override;
160     void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
161     void valueTreeChildOrderChanged (ValueTree&, int, int) override;
162     void valueTreeParentChanged (ValueTree&) override;
163     void valueTreeRedirected (ValueTree&) override;
164 
165     void codeDocumentTextInserted (const String&, int) override;
166     void codeDocumentTextDeleted (int, int) override;
167 
168     void setEditor (GenericCodeEditorComponent*);
169     void updateColourScheme();
170     void checkSaveState();
171 
172     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SourceCodeEditor)
173 };
174 
175 
176 //==============================================================================
177 class GenericCodeEditorComponent  : public CodeEditorComponent
178 {
179 public:
180     GenericCodeEditorComponent (const File&, CodeDocument&, CodeTokeniser*);
181     ~GenericCodeEditorComponent() override;
182 
183     void addPopupMenuItems (PopupMenu&, const MouseEvent*) override;
184     void performPopupMenuAction (int menuItemID) override;
185 
186     void getAllCommands (Array<CommandID>&) override;
187     void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
188     bool perform (const InvocationInfo&) override;
189 
190     void showFindPanel();
191     void hideFindPanel();
192     void findSelection();
193     void findNext (bool forwards, bool skipCurrentSelection);
194     void handleEscapeKey() override;
195     void editorViewportPositionChanged() override;
196 
197     void resized() override;
198 
getSearchString()199     static String getSearchString()                 { return getAppSettings().getGlobalProperties().getValue ("searchString"); }
setSearchString(const String & s)200     static void setSearchString (const String& s)   { getAppSettings().getGlobalProperties().setValue ("searchString", s); }
isCaseSensitiveSearch()201     static bool isCaseSensitiveSearch()             { return getAppSettings().getGlobalProperties().getBoolValue ("searchCaseSensitive"); }
setCaseSensitiveSearch(bool b)202     static void setCaseSensitiveSearch (bool b)     { getAppSettings().getGlobalProperties().setValue ("searchCaseSensitive", b); }
203 
204     struct Listener
205     {
~ListenerListener206         virtual ~Listener() {}
207         virtual void codeEditorViewportMoved (CodeEditorComponent&) = 0;
208     };
209 
210     void addListener (Listener* listener);
211     void removeListener (Listener* listener);
212 
213 private:
214     File file;
215     class FindPanel;
216     std::unique_ptr<FindPanel> findPanel;
217     ListenerList<Listener> listeners;
218 
219     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericCodeEditorComponent)
220 };
221 
222 //==============================================================================
223 class CppCodeEditorComponent  : public GenericCodeEditorComponent
224 {
225 public:
226     CppCodeEditorComponent (const File&, CodeDocument&);
227     ~CppCodeEditorComponent() override;
228 
229     void addPopupMenuItems (PopupMenu&, const MouseEvent*) override;
230     void performPopupMenuAction (int menuItemID) override;
231 
232     void handleReturnKey() override;
233     void insertTextAtCaret (const String& newText) override;
234 
235 private:
236     void insertComponentClass();
237 
238     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent)
239 };
240