mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-18 06:55:48 +03:00
Added basic linter support for ecode.
This commit is contained in:
19
bin/assets/linters/linters.json
Normal file
19
bin/assets/linters/linters.json
Normal file
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{
|
||||
"file_patterns": ["%.php$"],
|
||||
"warning_pattern": "[%a ]+:%s+(.*)%s+in%s.*on%sline%s+(%d+)",
|
||||
"warning_pattern_order": { "line": 2, "col": 0, "message": 1 },
|
||||
"command": "php -l $FILENAME"
|
||||
},
|
||||
{
|
||||
"file_patterns": ["%.js$", "%.ts$"],
|
||||
"warning_pattern": "[^:]:(%d+):(%d+): ([^%[]+)%[([^\n]+)",
|
||||
"warning_pattern_order": { "line": 1, "col": 2, "message": 3, "type": 4 },
|
||||
"command": "eslint --no-ignore --format unix $FILENAME"
|
||||
},
|
||||
{
|
||||
"file_patterns": ["%.lua$"],
|
||||
"warning_pattern": "[^:]:(%d+):(%d+):[%s]?([^\n]+)",
|
||||
"command": "luacheck $FILENAME --formatter=plain -g --no-max-line-length"
|
||||
}
|
||||
]
|
||||
@@ -404,7 +404,9 @@ class EE_API Node : public Transformable {
|
||||
|
||||
Uint32 forceTextInput( const TextInputEvent& Event );
|
||||
|
||||
protected:
|
||||
const Vector2f& getScreenPos() const;
|
||||
|
||||
protected:
|
||||
typedef std::map<Uint32, std::map<Uint32, EventCallback>> EventsMap;
|
||||
friend class EventDispatcher;
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class EE_API TextDocument {
|
||||
|
||||
bool save( const std::string& path );
|
||||
|
||||
bool save( IOStream& stream );
|
||||
bool save( IOStream& stream, bool keepUndoRedoStatus = false );
|
||||
|
||||
std::string getFilename() const;
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ class UICodeEditorModule {
|
||||
}
|
||||
virtual bool onMouseOver( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
|
||||
virtual bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
|
||||
|
||||
virtual void drawBeforeLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
|
||||
const Float& ){};
|
||||
virtual void drawAfterLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
|
||||
const Float& ){};
|
||||
};
|
||||
|
||||
class EE_API DocEvent : public Event {
|
||||
@@ -411,13 +416,13 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
||||
SyntaxHighlighter mHighlighter;
|
||||
UIScrollBar* mVScrollBar;
|
||||
UIScrollBar* mHScrollBar;
|
||||
LastXOffset mLastXOffset{{0, 0}, 0.f};
|
||||
LastXOffset mLastXOffset{ { 0, 0 }, 0.f };
|
||||
KeyBindings mKeyBindings;
|
||||
std::unordered_set<std::string> mUnlockedCmd;
|
||||
Clock mLastDoubleClick;
|
||||
Uint32 mLineBreakingColumn{100};
|
||||
Uint32 mLineBreakingColumn{ 100 };
|
||||
TextRange mMatchingBrackets;
|
||||
Float mLongestLineWidth{0};
|
||||
Float mLongestLineWidth{ 0 };
|
||||
Time mFindLongestLineWidthUpdateFrequency;
|
||||
Clock mLongestLineWidthLastUpdate;
|
||||
String mHighlightWord;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
../../bin/assets/layouts/test.xml
|
||||
../../bin/assets/layouts/test2.xml
|
||||
../../bin/assets/layouts/test_widgets.xml
|
||||
../../bin/assets/linters/linters.json
|
||||
../../bin/assets/ui/breeze.css
|
||||
../../bin/assets/ui/uitheme.css
|
||||
../../docs/articles/cssspecification.md
|
||||
@@ -1017,6 +1018,79 @@
|
||||
../../src/thirdparty/SOIL2/src/SOIL2/stbi_pkm.h
|
||||
../../src/thirdparty/SOIL2/src/SOIL2/stbi_pvr_c.h
|
||||
../../src/thirdparty/SOIL2/src/SOIL2/stbi_pvr.h
|
||||
../../src/thirdparty/efsw/include/efsw/efsw.h
|
||||
../../src/thirdparty/efsw/include/efsw/efsw.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Debug.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/Debug.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirWatcherGeneric.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirWatcherGeneric.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirectorySnapshot.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirectorySnapshot.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirectorySnapshotDiff.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/DirectorySnapshotDiff.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileInfo.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileInfo.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileSystem.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileSystem.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcher.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherCWrapper.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherFSEvents.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherGeneric.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherGeneric.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherInotify.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherInotify.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherKqueue.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherKqueue.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherWin32.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/FileWatcherWin32.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Lock.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Log.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/Mutex.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/Mutex.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/String.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/String.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/System.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/System.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Thread.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/Thread.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Utf.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/Utf.inl
|
||||
../../src/thirdparty/efsw/src/efsw/Watcher.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/Watcher.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherFSEvents.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherFSEvents.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherGeneric.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherGeneric.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherInotify.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherInotify.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherKqueue.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherKqueue.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherWin32.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/WatcherWin32.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/base.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/inotify-nosys.h
|
||||
../../src/thirdparty/efsw/src/efsw/platform/platformimpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/FileSystemImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/FileSystemImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/MutexImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/MutexImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/SystemImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/SystemImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/ThreadImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/posix/ThreadImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/FileSystemImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/FileSystemImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/MutexImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/MutexImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/SystemImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/SystemImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/ThreadImpl.cpp
|
||||
../../src/thirdparty/efsw/src/efsw/platform/win/ThreadImpl.hpp
|
||||
../../src/thirdparty/efsw/src/efsw/sophist.h
|
||||
../../src/thirdparty/efsw/src/test/efsw-test.cpp
|
||||
../../src/tools/codeeditor/appconfig.cpp
|
||||
../../src/tools/codeeditor/appconfig.hpp
|
||||
../../src/tools/codeeditor/autocompletemodule.cpp
|
||||
@@ -1027,6 +1101,8 @@
|
||||
../../src/tools/codeeditor/filesystemlistener.hpp
|
||||
../../src/tools/codeeditor/ignorematcher.cpp
|
||||
../../src/tools/codeeditor/ignorematcher.hpp
|
||||
../../src/tools/codeeditor/lintermodule.cpp
|
||||
../../src/tools/codeeditor/lintermodule.hpp
|
||||
../../src/tools/codeeditor/projectdirectorytree.cpp
|
||||
../../src/tools/codeeditor/projectdirectorytree.hpp
|
||||
../../src/tools/codeeditor/projectsearch.cpp
|
||||
|
||||
@@ -542,6 +542,10 @@ Uint32 Node::forceTextInput( const TextInputEvent& event ) {
|
||||
return onTextInput( event );
|
||||
}
|
||||
|
||||
const Vector2f& Node::getScreenPos() const {
|
||||
return mScreenPos;
|
||||
}
|
||||
|
||||
void Node::clipStart() {
|
||||
if ( mVisible && isClipped() ) {
|
||||
clipSmartEnable( mScreenPos.x, mScreenPos.y, mSize.getWidth(), mSize.getHeight() );
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace EE { namespace UI { namespace Doc {
|
||||
|
||||
// Color schemes are compatible with the lite (https://github.com/rxi/lite) color schemes.
|
||||
// But I also added:
|
||||
// "link" (link style)
|
||||
// "line_number_background" (the gutter background color)
|
||||
// "whitespace" (the whitespace color)
|
||||
// "line_break_column" (the right margin line column color)
|
||||
@@ -23,40 +24,42 @@ namespace EE { namespace UI { namespace Doc {
|
||||
// "matching_search" (the background color drawn in the text matching the current searched text)
|
||||
// "suggestion" (the auto-complete suggestion box text and background color
|
||||
// "suggestion_selected" (the auto-complete selected suggestion box text and background color
|
||||
// "error" (error underline color)
|
||||
// "warning" (warning underline color)
|
||||
|
||||
SyntaxColorScheme SyntaxColorScheme::getDefault() {
|
||||
return {"lite",
|
||||
{
|
||||
{"normal", Color( "#e1e1e6" )},
|
||||
{"symbol", Color( "#e1e1e6" )},
|
||||
{"comment", Color( "#676b6f" )},
|
||||
{"keyword", Color( "#E58AC9" )},
|
||||
{"keyword2", Color( "#F77483" )},
|
||||
{"number", Color( "#FFA94D" )},
|
||||
{"literal", Color( "#FFA94D" )},
|
||||
{"string", Color( "#f7c95c" )},
|
||||
{"operator", Color( "#93DDFA" )},
|
||||
{"function", Color( "#93DDFA" )},
|
||||
{"link", {Color( "#93DDFA" ), Color::Transparent, Text::Underlined}},
|
||||
},
|
||||
{
|
||||
{"background", Color( "#2e2e32" )},
|
||||
{"text", Color( "#97979c" )},
|
||||
{"caret", Color( "#93DDFA" )},
|
||||
{"selection", Color( "#48484f" )},
|
||||
{"line_highlight", Color( "#343438" )},
|
||||
{"line_number", Color( "#525259" )},
|
||||
{"line_number2", Color( "#83838f" )},
|
||||
// eepp colors
|
||||
{"line_number_background", Color( "#2e2e32" )},
|
||||
{"whitespace", Color( "#54575b" )},
|
||||
{"line_break_column", Color( "#54575b99" )},
|
||||
{"matching_bracket", Color( "#FFFFFF33" )},
|
||||
{"matching_selection", Color( "#FFFFFF33" )},
|
||||
{"matching_search", Color( "#181b1e" )},
|
||||
{"suggestion", {Color( "#97979c" ), Color( "#252529" ), Text::Regular}},
|
||||
{"suggestion_selected", {Color( "#e1e1e6" ), Color( "#252529" ), Text::Regular}},
|
||||
}};
|
||||
return { "lite",
|
||||
{
|
||||
{ "normal", Color( "#e1e1e6" ) },
|
||||
{ "symbol", Color( "#e1e1e6" ) },
|
||||
{ "comment", Color( "#676b6f" ) },
|
||||
{ "keyword", Color( "#E58AC9" ) },
|
||||
{ "keyword2", Color( "#F77483" ) },
|
||||
{ "number", Color( "#FFA94D" ) },
|
||||
{ "literal", Color( "#FFA94D" ) },
|
||||
{ "string", Color( "#f7c95c" ) },
|
||||
{ "operator", Color( "#93DDFA" ) },
|
||||
{ "function", Color( "#93DDFA" ) },
|
||||
{ "link", { Color( "#93DDFA" ), Color::Transparent, Text::Underlined } },
|
||||
},
|
||||
{ { "background", Color( "#2e2e32" ) },
|
||||
{ "text", Color( "#97979c" ) },
|
||||
{ "caret", Color( "#93DDFA" ) },
|
||||
{ "selection", Color( "#48484f" ) },
|
||||
{ "line_highlight", Color( "#343438" ) },
|
||||
{ "line_number", Color( "#525259" ) },
|
||||
{ "line_number2", Color( "#83838f" ) },
|
||||
// eepp colors
|
||||
{ "line_number_background", Color( "#2e2e32" ) },
|
||||
{ "whitespace", Color( "#54575b" ) },
|
||||
{ "line_break_column", Color( "#54575b99" ) },
|
||||
{ "matching_bracket", Color( "#FFFFFF33" ) },
|
||||
{ "matching_selection", Color( "#FFFFFF33" ) },
|
||||
{ "matching_search", Color( "#181b1e" ) },
|
||||
{ "suggestion", { Color( "#97979c" ), Color( "#252529" ), Text::Regular } },
|
||||
{ "suggestion_selected", { Color( "#e1e1e6" ), Color( "#252529" ), Text::Regular } },
|
||||
{ "error", { Color::Red } },
|
||||
{ "warning", { Color::Yellow } } } };
|
||||
}
|
||||
|
||||
std::vector<SyntaxColorScheme> SyntaxColorScheme::loadFromStream( IOStream& stream ) {
|
||||
@@ -152,7 +155,7 @@ SyntaxColorScheme::SyntaxColorScheme( const std::string& name,
|
||||
const std::unordered_map<std::string, Style>& editorColors ) :
|
||||
mName( name ), mSyntaxColors( syntaxColors ), mEditorColors( editorColors ) {}
|
||||
|
||||
static const SyntaxColorScheme::Style StyleEmpty = {Color::White};
|
||||
static const SyntaxColorScheme::Style StyleEmpty = { Color::White };
|
||||
static const SyntaxColorScheme StyleDefault = SyntaxColorScheme::getDefault();
|
||||
|
||||
const SyntaxColorScheme::Style& SyntaxColorScheme::getSyntaxStyle( const std::string& type ) const {
|
||||
@@ -187,6 +190,10 @@ SyntaxColorScheme::getEditorSyntaxStyle( const std::string& type ) const {
|
||||
return StyleDefault.getEditorSyntaxStyle( "suggestion" );
|
||||
else if ( type == "suggestion_selected" )
|
||||
return StyleDefault.getEditorSyntaxStyle( "suggestion_selected" );
|
||||
else if ( type == "error" )
|
||||
return StyleDefault.getEditorSyntaxStyle( "error" );
|
||||
else if ( type == "warning" )
|
||||
return StyleDefault.getEditorSyntaxStyle( "warning" );
|
||||
return StyleEmpty;
|
||||
}
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ bool TextDocument::save( const std::string& path ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextDocument::save( IOStream& stream ) {
|
||||
bool TextDocument::save( IOStream& stream, bool keepUndoRedoStatus ) {
|
||||
if ( !stream.isOpen() || mLines.empty() )
|
||||
return false;
|
||||
const std::string whitespaces( " \t\f\v\n\r" );
|
||||
@@ -378,7 +378,8 @@ bool TextDocument::save( IOStream& stream ) {
|
||||
stream.write( text.c_str(), text.size() );
|
||||
}
|
||||
}
|
||||
cleanChangeId();
|
||||
if ( !keepUndoRedoStatus )
|
||||
cleanChangeId();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +257,15 @@ void UICodeEditor::draw() {
|
||||
}
|
||||
|
||||
for ( int i = lineRange.first; i <= lineRange.second; i++ ) {
|
||||
for ( auto& module : mModules )
|
||||
module->drawBeforeLineText( this, i, { startScroll.x, startScroll.y + lineHeight * i },
|
||||
charSize, lineHeight );
|
||||
|
||||
drawLineText( i, { startScroll.x, startScroll.y + lineHeight * i }, charSize, lineHeight );
|
||||
|
||||
for ( auto& module : mModules )
|
||||
module->drawAfterLineText( this, i, { startScroll.x, startScroll.y + lineHeight * i },
|
||||
charSize, lineHeight );
|
||||
}
|
||||
|
||||
drawCursor( startScroll, lineHeight, cursor );
|
||||
|
||||
@@ -69,6 +69,7 @@ void AppConfig::load( std::string& confPath, std::string& keybindingsPath,
|
||||
editor.colorPickerSelection = ini.getValueB( "editor", "color_picker_selection", true );
|
||||
editor.colorPreview = ini.getValueB( "editor", "color_preview", true );
|
||||
editor.autoComplete = ini.getValueB( "editor", "auto_complete", true );
|
||||
editor.linter = ini.getValueB( "editor", "linter", true );
|
||||
editor.showDocInfo = ini.getValueB( "editor", "show_doc_info", true );
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
|
||||
ini.setValueB( "editor", "color_picker_selection", editor.colorPickerSelection );
|
||||
ini.setValueB( "editor", "color_preview", editor.colorPreview );
|
||||
ini.setValueB( "editor", "auto_complete", editor.autoComplete );
|
||||
ini.setValueB( "editor", "linter", editor.linter );
|
||||
ini.setValueB( "editor", "show_doc_info", editor.showDocInfo );
|
||||
ini.writeFile();
|
||||
iniState.writeFile();
|
||||
|
||||
@@ -50,6 +50,7 @@ struct CodeEditorConfig {
|
||||
bool colorPreview{ false };
|
||||
bool autoComplete{ true };
|
||||
bool showDocInfo{ true };
|
||||
bool linter{ true };
|
||||
std::string autoCloseBrackets{ "" };
|
||||
int indentWidth{ 4 };
|
||||
int tabWidth{ 4 };
|
||||
|
||||
@@ -90,7 +90,7 @@ void AutoCompleteModule::onRegister( UICodeEditor* editor ) {
|
||||
#endif
|
||||
} ) );
|
||||
|
||||
mEditors.insert( {editor, listeners} );
|
||||
mEditors.insert( { editor, listeners } );
|
||||
mDocs.insert( editor->getDocumentRef().get() );
|
||||
mEditorDocs[editor] = editor->getDocumentRef().get();
|
||||
mDirty = true;
|
||||
@@ -233,7 +233,7 @@ void AutoCompleteModule::pickSuggestion( UICodeEditor* editor ) {
|
||||
std::string AutoCompleteModule::getPartialSymbol( TextDocument* doc ) {
|
||||
TextPosition end = doc->getSelection().end();
|
||||
TextPosition start = doc->startOfWord( end );
|
||||
return doc->getText( {start, end} ).toUtf8();
|
||||
return doc->getText( { start, end } ).toUtf8();
|
||||
}
|
||||
|
||||
void AutoCompleteModule::update( UICodeEditor* ) {
|
||||
@@ -283,7 +283,7 @@ void AutoCompleteModule::postDraw( UICodeEditor* editor, const Vector2f& startSc
|
||||
largestString = eemax<size_t>( largestString, editor->getTextWidth( suggestions[i] ) );
|
||||
|
||||
mBoxRect =
|
||||
Rectf( Vector2f( cursorPos.x, cursorPos.y ),
|
||||
Rectf( Vector2f( cursorPos.x, cursorPos.y ) - editor->getScreenPos(),
|
||||
Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right, mRowHeight * max ) );
|
||||
|
||||
for ( size_t i = 0; i < max; i++ ) {
|
||||
@@ -321,7 +321,7 @@ bool AutoCompleteModule::onMouseClick( UICodeEditor* editor, const Vector2i& pos
|
||||
|
||||
Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) );
|
||||
if ( mBoxRect.contains( localPos ) ) {
|
||||
localPos -= {mBoxRect.Left, mBoxRect.Top};
|
||||
localPos -= { mBoxRect.Left, mBoxRect.Top };
|
||||
mSuggestionIndex = localPos.y / mRowHeight;
|
||||
editor->invalidateDraw();
|
||||
return true;
|
||||
@@ -433,7 +433,7 @@ static std::vector<std::string> fuzzyMatchSymbols( const AutoCompleteModule::Sym
|
||||
int score;
|
||||
for ( const auto& symbol : symbols ) {
|
||||
if ( ( score = String::fuzzyMatch( symbol, match ) ) > 0 ) {
|
||||
matchesMap.insert( {score, symbol} );
|
||||
matchesMap.insert( { score, symbol } );
|
||||
}
|
||||
}
|
||||
for ( auto& res : matchesMap ) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "codeeditor.hpp"
|
||||
#include "autocompletemodule.hpp"
|
||||
#include "lintermodule.hpp"
|
||||
#include <algorithm>
|
||||
#include <args/args.hxx>
|
||||
|
||||
@@ -947,6 +948,7 @@ App::~App() {
|
||||
saveConfig();
|
||||
eeSAFE_DELETE( mEditorSplitter );
|
||||
eeSAFE_DELETE( mAutoCompleteModule );
|
||||
eeSAFE_DELETE( mLinterModule );
|
||||
eeSAFE_DELETE( mConsole );
|
||||
if ( mFileWatcher )
|
||||
delete mFileWatcher;
|
||||
@@ -1180,6 +1182,10 @@ UIMenu* App::createViewMenu() {
|
||||
->setActive( mConfig.editor.autoComplete )
|
||||
->setTooltipText( "Auto complete shows the completion popup as you type, so you can fill\n"
|
||||
"in long words by typing only a few characters." );
|
||||
mViewMenu->addCheckBox( "Enable Linter" )
|
||||
->setActive( mConfig.editor.linter )
|
||||
->setTooltipText( "Use static code analysis tool used to flag programming errors, bugs,\n"
|
||||
"stylistic errors, and suspicious constructs." );
|
||||
mViewMenu->add( "Line Breaking Column" );
|
||||
|
||||
mViewMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) {
|
||||
@@ -1223,6 +1229,8 @@ UIMenu* App::createViewMenu() {
|
||||
} );
|
||||
} else if ( item->getText() == "Enable Auto Complete" ) {
|
||||
setAutoComplete( item->asType<UIMenuCheckBox>()->isActive() );
|
||||
} else if ( item->getText() == "Enable Linter" ) {
|
||||
setLinter( item->asType<UIMenuCheckBox>()->isActive() );
|
||||
} else if ( item->getText() == "Enable Color Preview" ) {
|
||||
mConfig.editor.colorPreview = item->asType<UIMenuCheckBox>()->isActive();
|
||||
mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) {
|
||||
@@ -1908,6 +1916,12 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
|
||||
|
||||
if ( config.autoComplete && mAutoCompleteModule )
|
||||
editor->registerModule( mAutoCompleteModule );
|
||||
|
||||
if ( config.linter && !mLinterModule )
|
||||
setLinter( config.linter );
|
||||
|
||||
if ( config.linter && mLinterModule )
|
||||
editor->registerModule( mLinterModule );
|
||||
}
|
||||
|
||||
bool App::setAutoComplete( bool enable ) {
|
||||
@@ -1923,6 +1937,20 @@ bool App::setAutoComplete( bool enable ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::setLinter( bool enable ) {
|
||||
mConfig.editor.linter = enable;
|
||||
if ( enable && !mLinterModule ) {
|
||||
mLinterModule =
|
||||
eeNew( LinterModule, ( mResPath + "assets/linters/linters.json", mThreadPool ) );
|
||||
mEditorSplitter->forEachEditor(
|
||||
[&]( UICodeEditor* editor ) { editor->registerModule( mLinterModule ); } );
|
||||
return true;
|
||||
}
|
||||
if ( !enable && mLinterModule )
|
||||
eeSAFE_DELETE( mLinterModule );
|
||||
return false;
|
||||
}
|
||||
|
||||
void App::loadCurrentDirectory() {
|
||||
if ( !mEditorSplitter->getCurEditor() )
|
||||
return;
|
||||
|
||||
@@ -94,6 +94,7 @@ struct SearchState {
|
||||
};
|
||||
|
||||
class AutoCompleteModule;
|
||||
class LinterModule;
|
||||
|
||||
class App : public UICodeEditorSplitter::Client {
|
||||
public:
|
||||
@@ -181,6 +182,7 @@ class App : public UICodeEditorSplitter::Client {
|
||||
Float mDisplayDPI;
|
||||
std::string mResPath;
|
||||
AutoCompleteModule* mAutoCompleteModule{ nullptr };
|
||||
LinterModule* mLinterModule{ nullptr };
|
||||
std::shared_ptr<ThreadPool> mThreadPool;
|
||||
std::unique_ptr<ProjectDirectoryTree> mDirTree;
|
||||
UITreeView* mProjectTreeView{ nullptr };
|
||||
@@ -291,6 +293,8 @@ class App : public UICodeEditorSplitter::Client {
|
||||
|
||||
bool setAutoComplete( bool enable );
|
||||
|
||||
bool setLinter( bool enable );
|
||||
|
||||
void updateDocInfo( TextDocument& doc );
|
||||
|
||||
void setFocusEditorOnClose( UIMessageBox* msgBox );
|
||||
|
||||
372
src/tools/codeeditor/lintermodule.cpp
Normal file
372
src/tools/codeeditor/lintermodule.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "lintermodule.hpp"
|
||||
#include "thirdparty/json.hpp"
|
||||
#include "thirdparty/subprocess.h"
|
||||
#include <algorithm>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/iostreamstring.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/ui/uitooltip.hpp>
|
||||
#include <random>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
|
||||
#define LINTER_THREADED 1
|
||||
#else
|
||||
#define LINTER_THREADED 0
|
||||
#endif
|
||||
|
||||
LinterModule::LinterModule( const std::string& lintersPath, std::shared_ptr<ThreadPool> pool ) :
|
||||
mPool( pool ) {
|
||||
#if LINTER_THREADED
|
||||
mPool->run( [&, lintersPath] { load( lintersPath ); }, [] {} );
|
||||
#else
|
||||
load( lintersPath );
|
||||
#endif
|
||||
}
|
||||
|
||||
LinterModule::~LinterModule() {
|
||||
mClosing = true;
|
||||
for ( auto editor : mEditors ) {
|
||||
for ( auto listener : editor.second )
|
||||
editor.first->removeEventListener( listener );
|
||||
editor.first->unregisterModule( this );
|
||||
}
|
||||
}
|
||||
|
||||
void LinterModule::load( const std::string& lintersPath ) {
|
||||
if ( !FileSystem::fileExists( lintersPath ) )
|
||||
return;
|
||||
try {
|
||||
std::ifstream stream( lintersPath );
|
||||
json j;
|
||||
stream >> j;
|
||||
|
||||
for ( auto& obj : j ) {
|
||||
Linter linter;
|
||||
auto fp = obj["file_patterns"];
|
||||
|
||||
for ( auto& pattern : fp )
|
||||
linter.files.push_back( pattern.get<std::string>() );
|
||||
|
||||
linter.warningPattern = obj["warning_pattern"].get<std::string>();
|
||||
linter.command = obj["command"].get<std::string>();
|
||||
|
||||
if ( obj.contains( "warning_pattern_order" ) ) {
|
||||
auto& wpo = obj["warning_pattern_order"];
|
||||
if ( wpo.contains( "line" ) )
|
||||
linter.warningPatternOrder.line = wpo["line"].get<int>();
|
||||
if ( wpo.contains( "col" ) )
|
||||
linter.warningPatternOrder.col = wpo["col"].get<int>();
|
||||
if ( wpo.contains( "message" ) )
|
||||
linter.warningPatternOrder.message = wpo["message"].get<int>();
|
||||
if ( wpo.contains( "type" ) )
|
||||
linter.warningPatternOrder.type = wpo["type"].get<int>();
|
||||
}
|
||||
|
||||
mLinters.emplace_back( std::move( linter ) );
|
||||
}
|
||||
|
||||
mReady = true;
|
||||
} catch ( json::exception& e ) {
|
||||
mReady = false;
|
||||
Log::error( "Parsing linter failed:\n%s", e.what() );
|
||||
}
|
||||
}
|
||||
|
||||
void LinterModule::onRegister( UICodeEditor* editor ) {
|
||||
Lock l( mDocMutex );
|
||||
|
||||
std::vector<Uint32> listeners;
|
||||
|
||||
listeners.push_back(
|
||||
editor->addEventListener( Event::OnDocumentLoaded, [&]( const Event* event ) {
|
||||
Lock l( mDocMutex );
|
||||
const DocEvent* docEvent = static_cast<const DocEvent*>( event );
|
||||
setDocDirty( docEvent->getDoc() );
|
||||
} ) );
|
||||
|
||||
listeners.push_back(
|
||||
editor->addEventListener( Event::OnDocumentClosed, [&]( const Event* event ) {
|
||||
Lock l( mDocMutex );
|
||||
const DocEvent* docEvent = static_cast<const DocEvent*>( event );
|
||||
TextDocument* doc = docEvent->getDoc();
|
||||
mDocs.erase( doc );
|
||||
mDirtyDoc.erase( doc );
|
||||
Lock matchesLock( mMatchesMutex );
|
||||
mMatches.erase( doc );
|
||||
} ) );
|
||||
|
||||
listeners.push_back(
|
||||
editor->addEventListener( Event::OnDocumentChanged, [&, editor]( const Event* ) {
|
||||
TextDocument* oldDoc = mEditorDocs[editor];
|
||||
TextDocument* newDoc = editor->getDocumentRef().get();
|
||||
Lock l( mDocMutex );
|
||||
mDocs.erase( oldDoc );
|
||||
mDirtyDoc.erase( oldDoc );
|
||||
mEditorDocs[editor] = newDoc;
|
||||
Lock matchesLock( mMatchesMutex );
|
||||
mMatches.erase( oldDoc );
|
||||
} ) );
|
||||
|
||||
listeners.push_back( editor->addEventListener(
|
||||
Event::OnTextChanged, [&, editor]( const Event* ) { setDocDirty( editor ); } ) );
|
||||
|
||||
mEditors.insert( { editor, listeners } );
|
||||
mDocs.insert( editor->getDocumentRef().get() );
|
||||
mEditorDocs[editor] = editor->getDocumentRef().get();
|
||||
}
|
||||
|
||||
void LinterModule::onUnregister( UICodeEditor* editor ) {
|
||||
if ( mClosing )
|
||||
return;
|
||||
Lock l( mDocMutex );
|
||||
TextDocument* doc = mEditorDocs[editor];
|
||||
auto cbs = mEditors[editor];
|
||||
for ( auto listener : cbs )
|
||||
editor->removeEventListener( listener );
|
||||
mEditors.erase( editor );
|
||||
mEditorDocs.erase( editor );
|
||||
for ( auto editor : mEditorDocs )
|
||||
if ( editor.second == doc )
|
||||
return;
|
||||
mDocs.erase( doc );
|
||||
mDirtyDoc.erase( doc );
|
||||
Lock matchesLock( mMatchesMutex );
|
||||
mMatches.erase( doc );
|
||||
}
|
||||
|
||||
void LinterModule::update( UICodeEditor* editor ) {
|
||||
TextDocument* doc = editor->getDocumentRef().get();
|
||||
auto it = mDirtyDoc.find( doc );
|
||||
if ( it != mDirtyDoc.end() && it->second->getElapsedTime() >= mDelayTime ) {
|
||||
mDirtyDoc.erase( doc );
|
||||
#if LINTER_THREADED
|
||||
mPool->run( [&, doc] { lintDoc( doc ); }, [] {} );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
const Time& LinterModule::getDelayTime() const {
|
||||
return mDelayTime;
|
||||
}
|
||||
|
||||
void LinterModule::setDelayTime( const Time& delayTime ) {
|
||||
mDelayTime = delayTime;
|
||||
}
|
||||
|
||||
static std::string randString( size_t len ) {
|
||||
std::string str( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" );
|
||||
std::random_device rd;
|
||||
std::mt19937 generator( rd() );
|
||||
std::shuffle( str.begin(), str.end(), generator );
|
||||
return str.substr( 0, len );
|
||||
}
|
||||
|
||||
void LinterModule::lintDoc( TextDocument* doc ) {
|
||||
auto linter = supportsLinter( doc );
|
||||
if ( linter.command.empty() )
|
||||
return;
|
||||
IOStreamString fileString;
|
||||
if ( doc->isDirty() || !doc->hasFilepath() ) {
|
||||
std::string tmpPath;
|
||||
if ( !doc->hasFilepath() ) {
|
||||
tmpPath = Sys::getTempPath() + ".ecode-" + doc->getFilename() + "." + randString( 8 );
|
||||
} else {
|
||||
std::string fileDir( FileSystem::fileRemoveFileName( doc->getFilePath() ) );
|
||||
FileSystem::dirAddSlashAtEnd( fileDir );
|
||||
tmpPath = fileDir + "." + randString( 8 ) + "." + doc->getFilename();
|
||||
}
|
||||
|
||||
doc->save( fileString, true );
|
||||
FileSystem::fileWrite( tmpPath, (Uint8*)fileString.getStreamPointer(),
|
||||
fileString.getSize() );
|
||||
runLinter( doc, linter, tmpPath );
|
||||
FileSystem::fileRemove( tmpPath );
|
||||
} else {
|
||||
runLinter( doc, linter, doc->getFilePath() );
|
||||
}
|
||||
}
|
||||
|
||||
void LinterModule::runLinter( TextDocument* doc, const Linter& linter, const std::string& path ) {
|
||||
Clock clock;
|
||||
std::string cmd( linter.command );
|
||||
String::replaceAll( cmd, "$FILENAME", path );
|
||||
std::vector<std::string> cmdArr = String::split( cmd, ' ' );
|
||||
std::vector<const char*> strings;
|
||||
for ( size_t i = 0; i < cmdArr.size(); ++i )
|
||||
strings.push_back( cmdArr[i].c_str() );
|
||||
strings.push_back( NULL );
|
||||
struct subprocess_s subprocess;
|
||||
int result = subprocess_create( strings.data(),
|
||||
subprocess_option_inherit_environment |
|
||||
subprocess_option_combined_stdout_stderr,
|
||||
&subprocess );
|
||||
if ( 0 == result ) {
|
||||
std::string buffer( 1024, '\0' );
|
||||
std::string data;
|
||||
unsigned index = 0;
|
||||
unsigned bytesRead = 0;
|
||||
do {
|
||||
bytesRead = subprocess_read_stdout( &subprocess, &buffer[0], buffer.size() );
|
||||
index += bytesRead;
|
||||
data += buffer.substr( 0, bytesRead );
|
||||
} while ( bytesRead != 0 );
|
||||
|
||||
int ret;
|
||||
subprocess_join( &subprocess, &ret );
|
||||
subprocess_destroy( &subprocess );
|
||||
|
||||
// Log::info( "Linter result:\n%s", data.c_str() );
|
||||
|
||||
LuaPattern pattern( linter.warningPattern );
|
||||
std::map<Int64, LinterMatch> matches;
|
||||
for ( auto& match : pattern.gmatch( data ) ) {
|
||||
LinterMatch linterMatch;
|
||||
std::string lineStr = match.group( linter.warningPatternOrder.line );
|
||||
std::string colStr = linter.warningPatternOrder.col >= 0
|
||||
? match.group( linter.warningPatternOrder.col )
|
||||
: "";
|
||||
linterMatch.text = match.group( linter.warningPatternOrder.message );
|
||||
|
||||
if ( linter.warningPatternOrder.type >= 0 ) {
|
||||
std::string type( match.group( linter.warningPatternOrder.type ) );
|
||||
String::toLowerInPlace( type );
|
||||
if ( String::startsWith( type, "warn" ) ) {
|
||||
linterMatch.type = LinterType::Warning;
|
||||
} else if ( String::startsWith( type, "notice" ) ) {
|
||||
linterMatch.type = LinterType::Notice;
|
||||
}
|
||||
}
|
||||
|
||||
Int64 line;
|
||||
Int64 col = 1;
|
||||
if ( !linterMatch.text.empty() && !lineStr.empty() &&
|
||||
String::fromString( line, lineStr ) ) {
|
||||
if ( !colStr.empty() )
|
||||
String::fromString( col, colStr );
|
||||
linterMatch.pos = { line - 1, col > 0 ? col - 1 : 0 };
|
||||
linterMatch.lineCache = doc->line( line - 1 ).getHash();
|
||||
matches.insert( { line - 1, std::move( linterMatch ) } );
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Lock matchesLock( mMatchesMutex );
|
||||
mMatches[doc] = matches;
|
||||
}
|
||||
|
||||
invalidateEditors( doc );
|
||||
|
||||
Log::info( "LinterModule::runLinter for %s took %.2fms", path.c_str(),
|
||||
clock.getElapsedTime().asMilliseconds() );
|
||||
}
|
||||
}
|
||||
|
||||
void LinterModule::drawAfterLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
||||
const Float& /*fontSize*/, const Float& lineHeight ) {
|
||||
mMatchesMutex.lock();
|
||||
auto matchIt = mMatches.find( editor->getDocumentRef().get() );
|
||||
if ( matchIt == mMatches.end() ) {
|
||||
mMatchesMutex.unlock();
|
||||
return;
|
||||
}
|
||||
mMatchesMutex.unlock();
|
||||
|
||||
std::map<Int64, LinterMatch>& map = matchIt->second;
|
||||
auto lineIt = map.find( index );
|
||||
if ( lineIt == map.end() )
|
||||
return;
|
||||
TextDocument* doc = matchIt->first;
|
||||
LinterMatch& match = lineIt->second;
|
||||
if ( match.lineCache != doc->line( index ).getHash() )
|
||||
return;
|
||||
Text line( "", editor->getFont(), editor->getFontSize() );
|
||||
line.setTabWidth( editor->getTabWidth() );
|
||||
line.setStyleConfig( editor->getFontStyleConfig() );
|
||||
line.setColor( editor->getColorScheme()
|
||||
.getEditorSyntaxStyle( match.type == LinterType::Warning ||
|
||||
match.type == LinterType::Notice
|
||||
? "warning"
|
||||
: "error" )
|
||||
.color );
|
||||
const String& text = doc->line( index ).getText();
|
||||
size_t minCol = text.find_first_not_of( " \t\f\v\n\r", match.pos.column() );
|
||||
if ( minCol == String::InvalidPos )
|
||||
match.pos.column();
|
||||
minCol = std::max( (Int64)minCol, match.pos.column() );
|
||||
|
||||
std::string str( text.substr( minCol ).size() - 1, '~' );
|
||||
String string( str );
|
||||
line.setString( string );
|
||||
|
||||
Vector2f pos( position.x + editor->getXOffsetCol( { match.pos.line(), (Int64)minCol } ),
|
||||
position.y );
|
||||
Rectf box( pos - editor->getScreenPos(), { editor->getTextWidth( string ), lineHeight } );
|
||||
match.box = box;
|
||||
line.draw( pos.x, pos.y + lineHeight * 0.5f );
|
||||
}
|
||||
|
||||
bool LinterModule::onMouseMove( UICodeEditor* editor, const Vector2i& pos, const Uint32& ) {
|
||||
auto it = mMatches.find( editor->getDocumentRef().get() );
|
||||
if ( it != mMatches.end() ) {
|
||||
Vector2f localPos( editor->convertToNodeSpace( pos.asFloat() ) );
|
||||
for ( auto matchIt : it->second ) {
|
||||
auto& match = matchIt.second;
|
||||
if ( match.box.contains( localPos ) ) {
|
||||
editor->setTooltipText( match.text );
|
||||
editor->getTooltip()->setPixelsPosition( Vector2f( pos.x, pos.y ) );
|
||||
editor->runOnMainThread( [&, editor] { editor->getTooltip()->show(); } );
|
||||
return false;
|
||||
} else if ( editor->getTooltip() && editor->getTooltip()->isVisible() ) {
|
||||
editor->setTooltipText( "" );
|
||||
editor->getTooltip()->hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinterModule::onMouseLeave( UICodeEditor* editor, const Vector2i&, const Uint32& ) {
|
||||
if ( editor->getTooltip() && editor->getTooltip()->isVisible() ) {
|
||||
editor->setTooltipText( "" );
|
||||
editor->getTooltip()->hide();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Linter LinterModule::supportsLinter( TextDocument* doc ) {
|
||||
std::string filePath( doc->getFilePath() );
|
||||
std::string extension( FileSystem::fileExtension( filePath ) );
|
||||
if ( extension.empty() ) {
|
||||
extension = FileSystem::fileNameFromPath( filePath );
|
||||
}
|
||||
const auto& def = doc->getSyntaxDefinition();
|
||||
|
||||
for ( auto& linter : mLinters ) {
|
||||
for ( auto& ext : linter.files ) {
|
||||
auto& files = def.getFiles();
|
||||
if ( std::find( files.begin(), files.end(), ext ) != files.end() ) {
|
||||
return linter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void LinterModule::setDocDirty( TextDocument* doc ) {
|
||||
mDirtyDoc[doc] = std::make_unique<Clock>();
|
||||
}
|
||||
void LinterModule::setDocDirty( UICodeEditor* editor ) {
|
||||
mDirtyDoc[editor->getDocumentRef().get()] = std::make_unique<Clock>();
|
||||
}
|
||||
|
||||
void LinterModule::invalidateEditors( TextDocument* doc ) {
|
||||
Lock l( mDocMutex );
|
||||
for ( auto& it : mEditorDocs ) {
|
||||
if ( it.second == doc )
|
||||
it.first->invalidateDraw();
|
||||
}
|
||||
}
|
||||
93
src/tools/codeeditor/lintermodule.hpp
Normal file
93
src/tools/codeeditor/lintermodule.hpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef EE_TOOLS_LINTER_HPP
|
||||
#define EE_TOOLS_LINTER_HPP
|
||||
|
||||
#include <eepp/config.hpp>
|
||||
#include <eepp/system/mutex.hpp>
|
||||
#include <eepp/system/threadpool.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
#include <set>
|
||||
using namespace EE;
|
||||
using namespace EE::System;
|
||||
using namespace EE::UI;
|
||||
|
||||
enum class LinterType {
|
||||
Notice,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
struct Linter {
|
||||
std::vector<std::string> files;
|
||||
std::string warningPattern;
|
||||
struct {
|
||||
int line{ 1 };
|
||||
int col{ 2 };
|
||||
int message{ 3 };
|
||||
int type{ -1 };
|
||||
} warningPatternOrder;
|
||||
std::string command;
|
||||
};
|
||||
|
||||
struct LinterMatch {
|
||||
std::string text;
|
||||
TextPosition pos;
|
||||
String::HashType lineCache;
|
||||
Rectf box;
|
||||
LinterType type{ LinterType::Error };
|
||||
};
|
||||
|
||||
class LinterModule : public UICodeEditorModule {
|
||||
public:
|
||||
LinterModule( const std::string& lintersPath, std::shared_ptr<ThreadPool> pool );
|
||||
|
||||
virtual ~LinterModule();
|
||||
|
||||
void onRegister( UICodeEditor* );
|
||||
|
||||
void onUnregister( UICodeEditor* );
|
||||
|
||||
void drawAfterLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
||||
const Float& fontSize, const Float& lineHeight );
|
||||
|
||||
void update( UICodeEditor* );
|
||||
|
||||
bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& );
|
||||
|
||||
bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& );
|
||||
|
||||
const Time& getDelayTime() const;
|
||||
|
||||
void setDelayTime( const Time& delayTime );
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ThreadPool> mPool;
|
||||
std::vector<Linter> mLinters;
|
||||
std::unordered_map<UICodeEditor*, std::vector<Uint32>> mEditors;
|
||||
std::set<TextDocument*> mDocs;
|
||||
std::unordered_map<UICodeEditor*, TextDocument*> mEditorDocs;
|
||||
std::unordered_map<TextDocument*, std::unique_ptr<Clock>> mDirtyDoc;
|
||||
std::unordered_map<TextDocument*, std::map<Int64, LinterMatch>> mMatches;
|
||||
Time mDelayTime{ Seconds( 0.5f ) };
|
||||
Mutex mDocMutex;
|
||||
Mutex mMatchesMutex;
|
||||
|
||||
bool mReady{ false };
|
||||
bool mClosing{ false };
|
||||
|
||||
void load( const std::string& lintersPath );
|
||||
|
||||
void lintDoc( TextDocument* doc );
|
||||
|
||||
void runLinter( TextDocument* doc, const Linter& linter, const std::string& path );
|
||||
|
||||
Linter supportsLinter( TextDocument* doc );
|
||||
|
||||
void setDocDirty( TextDocument* doc );
|
||||
|
||||
void setDocDirty( UICodeEditor* editor );
|
||||
|
||||
void invalidateEditors( TextDocument* doc );
|
||||
|
||||
};
|
||||
|
||||
#endif // EE_TOOLS_LINTER_HPP
|
||||
25557
src/tools/codeeditor/thirdparty/json.hpp
vendored
Normal file
25557
src/tools/codeeditor/thirdparty/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
857
src/tools/codeeditor/thirdparty/subprocess.h
vendored
Normal file
857
src/tools/codeeditor/thirdparty/subprocess.h
vendored
Normal file
@@ -0,0 +1,857 @@
|
||||
/*
|
||||
The latest version of this library is available on GitHub;
|
||||
https://github.com/sheredom/subprocess.h
|
||||
*/
|
||||
|
||||
/*
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
*/
|
||||
|
||||
#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED
|
||||
#define SHEREDOM_SUBPROCESS_H_INCLUDED
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push, 1)
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#define subprocess_pure __attribute__((pure))
|
||||
#define subprocess_weak __attribute__((weak))
|
||||
#elif defined(_MSC_VER)
|
||||
#define subprocess_pure
|
||||
#define subprocess_weak __inline
|
||||
#else
|
||||
#error Non clang, non gcc, non MSVC compiler found!
|
||||
#endif
|
||||
|
||||
struct subprocess_s;
|
||||
|
||||
enum subprocess_option_e {
|
||||
// stdout and stderr are the same FILE.
|
||||
subprocess_option_combined_stdout_stderr = 0x1,
|
||||
|
||||
// The child process should inherit the environment variables of the parent.
|
||||
subprocess_option_inherit_environment = 0x2,
|
||||
|
||||
// Enable asynchronous reading of stdout/stderr before it has completed.
|
||||
subprocess_option_enable_async = 0x4
|
||||
};
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// @brief Create a process.
|
||||
/// @param command_line An array of strings for the command line to execute for
|
||||
/// this process. The last element must be NULL to signify the end of the array.
|
||||
/// @param options A bit field of subprocess_option_e's to pass.
|
||||
/// @param out_process The newly created process.
|
||||
/// @return On success 0 is returned.
|
||||
subprocess_weak int subprocess_create(const char *const command_line[],
|
||||
int options,
|
||||
struct subprocess_s *const out_process);
|
||||
|
||||
/// @brief Get the standard input file for a process.
|
||||
/// @param process The process to query.
|
||||
/// @return The file for standard input of the process.
|
||||
///
|
||||
/// The file returned can be written to by the parent process to feed data to
|
||||
/// the standard input of the process.
|
||||
subprocess_pure subprocess_weak FILE *
|
||||
subprocess_stdin(const struct subprocess_s *const process);
|
||||
|
||||
/// @brief Get the standard output file for a process.
|
||||
/// @param process The process to query.
|
||||
/// @return The file for standard output of the process.
|
||||
///
|
||||
/// The file returned can be read from by the parent process to read data from
|
||||
/// the standard output of the child process.
|
||||
subprocess_pure subprocess_weak FILE *
|
||||
subprocess_stdout(const struct subprocess_s *const process);
|
||||
|
||||
/// @brief Get the standard error file for a process.
|
||||
/// @param process The process to query.
|
||||
/// @return The file for standard error of the process.
|
||||
///
|
||||
/// The file returned can be read from by the parent process to read data from
|
||||
/// the standard error of the child process.
|
||||
///
|
||||
/// If the process was created with the subprocess_option_combined_stdout_stderr
|
||||
/// option bit set, this function will return NULL, and the subprocess_stdout
|
||||
/// function should be used for both the standard output and error combined.
|
||||
subprocess_pure subprocess_weak FILE *
|
||||
subprocess_stderr(const struct subprocess_s *const process);
|
||||
|
||||
/// @brief Wait for a process to finish execution.
|
||||
/// @param process The process to wait for.
|
||||
/// @param out_return_code The return code of the returned process (can be
|
||||
/// NULL).
|
||||
/// @return On success 0 is returned.
|
||||
///
|
||||
/// Joining a process will close the stdin pipe to the process.
|
||||
subprocess_weak int subprocess_join(struct subprocess_s *const process,
|
||||
int *const out_return_code);
|
||||
|
||||
/// @brief Destroy a previously created process.
|
||||
/// @param process The process to destroy.
|
||||
/// @return On success 0 is returned.
|
||||
///
|
||||
/// If the process to be destroyed had not finished execution, it may out live
|
||||
/// the parent process.
|
||||
subprocess_weak int subprocess_destroy(struct subprocess_s *const process);
|
||||
|
||||
/// @brief Terminate a previously created process.
|
||||
/// @param process The process to terminate.
|
||||
/// @return On success 0 is returned.
|
||||
///
|
||||
/// If the process to be destroyed had not finished execution, it will be
|
||||
/// terminated (i.e killed).
|
||||
subprocess_weak int subprocess_terminate(struct subprocess_s *const process);
|
||||
|
||||
/// @brief Read the standard output from the child process.
|
||||
/// @param process The process to read from.
|
||||
/// @param buffer The buffer to read into.
|
||||
/// @param size The maximum number of bytes to read.
|
||||
/// @return The number of bytes actually read into buffer. Can only be 0 if the
|
||||
/// process has complete.
|
||||
///
|
||||
/// The only safe way to read from the standard output of a process during it's
|
||||
/// execution is to use the `subprocess_option_enable_async` option in
|
||||
/// conjuction with this method.
|
||||
subprocess_weak unsigned
|
||||
subprocess_read_stdout(struct subprocess_s *const process, char *const buffer,
|
||||
unsigned size);
|
||||
|
||||
/// @brief Read the standard error from the child process.
|
||||
/// @param process The process to read from.
|
||||
/// @param buffer The buffer to read into.
|
||||
/// @param size The maximum number of bytes to read.
|
||||
/// @return The number of bytes actually read into buffer. Can only be 0 if the
|
||||
/// process has complete.
|
||||
///
|
||||
/// The only safe way to read from the standard error of a process during it's
|
||||
/// execution is to use the `subprocess_option_enable_async` option in
|
||||
/// conjuction with this method.
|
||||
subprocess_weak unsigned
|
||||
subprocess_read_stderr(struct subprocess_s *const process, char *const buffer,
|
||||
unsigned size);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define SUBPROCESS_CAST(type, x) static_cast<type>(x)
|
||||
#else
|
||||
#define SUBPROCESS_CAST(type, x) ((type)x)
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#ifdef _WIN64
|
||||
typedef __int64 subprocess_intptr_t;
|
||||
typedef unsigned __int64 subprocess_size_t;
|
||||
#else
|
||||
typedef int subprocess_intptr_t;
|
||||
typedef unsigned int subprocess_size_t;
|
||||
#endif
|
||||
|
||||
typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
|
||||
typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
|
||||
typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
|
||||
typedef struct _OVERLAPPED *LPOVERLAPPED;
|
||||
|
||||
#pragma warning(push, 1)
|
||||
struct subprocess_subprocess_information_s {
|
||||
void *hProcess;
|
||||
void *hThread;
|
||||
unsigned long dwProcessId;
|
||||
unsigned long dwThreadId;
|
||||
};
|
||||
|
||||
struct subprocess_security_attributes_s {
|
||||
unsigned long nLength;
|
||||
void *lpSecurityDescriptor;
|
||||
int bInheritHandle;
|
||||
};
|
||||
|
||||
struct subprocess_startup_info_s {
|
||||
unsigned long cb;
|
||||
char *lpReserved;
|
||||
char *lpDesktop;
|
||||
char *lpTitle;
|
||||
unsigned long dwX;
|
||||
unsigned long dwY;
|
||||
unsigned long dwXSize;
|
||||
unsigned long dwYSize;
|
||||
unsigned long dwXCountChars;
|
||||
unsigned long dwYCountChars;
|
||||
unsigned long dwFillAttribute;
|
||||
unsigned long dwFlags;
|
||||
unsigned short wShowWindow;
|
||||
unsigned short cbReserved2;
|
||||
unsigned char *lpReserved2;
|
||||
void *hStdInput;
|
||||
void *hStdOutput;
|
||||
void *hStdError;
|
||||
};
|
||||
|
||||
struct subprocess_overlapped_s {
|
||||
uintptr_t Internal;
|
||||
uintptr_t InternalHigh;
|
||||
union {
|
||||
struct {
|
||||
unsigned long Offset;
|
||||
unsigned long OffsetHigh;
|
||||
} DUMMYSTRUCTNAME;
|
||||
void *Pointer;
|
||||
} DUMMYUNIONNAME;
|
||||
|
||||
void *hEvent;
|
||||
};
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
__declspec(dllimport) unsigned long __stdcall GetLastError(void);
|
||||
__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
|
||||
unsigned long);
|
||||
__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
|
||||
LPSECURITY_ATTRIBUTES,
|
||||
unsigned long);
|
||||
__declspec(dllimport) void *__stdcall CreateNamedPipeA(
|
||||
const char *, unsigned long, unsigned long, unsigned long, unsigned long,
|
||||
unsigned long, unsigned long, LPSECURITY_ATTRIBUTES);
|
||||
__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long,
|
||||
unsigned long *, LPOVERLAPPED);
|
||||
__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void);
|
||||
__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void);
|
||||
__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long,
|
||||
unsigned long,
|
||||
LPSECURITY_ATTRIBUTES,
|
||||
unsigned long, unsigned long,
|
||||
void *);
|
||||
__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int,
|
||||
int, const char *);
|
||||
__declspec(dllimport) int __stdcall CreateProcessA(
|
||||
const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
|
||||
unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
|
||||
__declspec(dllimport) int __stdcall CloseHandle(void *);
|
||||
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
|
||||
void *, unsigned long);
|
||||
__declspec(dllimport) int __stdcall GetExitCodeProcess(
|
||||
void *, unsigned long *lpExitCode);
|
||||
__declspec(dllimport) int __stdcall TerminateProcess(
|
||||
void *, unsigned int);
|
||||
__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects(
|
||||
unsigned long, void *const *, int, unsigned long);
|
||||
__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED,
|
||||
unsigned long *, int);
|
||||
|
||||
#if defined(_DLL) && (_DLL == 1)
|
||||
#define SUBPROCESS_DLLIMPORT __declspec(dllimport)
|
||||
#else
|
||||
#define SUBPROCESS_DLLIMPORT
|
||||
#endif
|
||||
|
||||
SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *);
|
||||
SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int);
|
||||
SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int);
|
||||
|
||||
void *__cdecl _alloca(subprocess_size_t);
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpadded"
|
||||
#endif
|
||||
struct subprocess_s {
|
||||
FILE *stdin_file;
|
||||
FILE *stdout_file;
|
||||
FILE *stderr_file;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
void *hProcess;
|
||||
void *hStdInput;
|
||||
void *hEventOutput;
|
||||
void *hEventError;
|
||||
#else
|
||||
pid_t child;
|
||||
#endif
|
||||
};
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr);
|
||||
int subprocess_create_named_pipe_helper(void **rd, void **wr) {
|
||||
const unsigned long pipeAccessInbound = 0x00000001;
|
||||
const unsigned long fileFlagOverlapped = 0x40000000;
|
||||
const unsigned long pipeTypeByte = 0x00000000;
|
||||
const unsigned long pipeWait = 0x00000000;
|
||||
const unsigned long genericWrite = 0x40000000;
|
||||
const unsigned long openExisting = 3;
|
||||
const unsigned long fileAttributeNormal = 0x00000080;
|
||||
const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0);
|
||||
struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
|
||||
char name[256] = {0};
|
||||
__declspec(thread) static long index = 0;
|
||||
const long unique = index++;
|
||||
|
||||
#if _MSC_VER < 1900
|
||||
#pragma warning(disable : 4996)
|
||||
#pragma warning(push, 1)
|
||||
|
||||
_snprintf(name, sizeof(name) - 1,
|
||||
"\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
|
||||
GetCurrentProcessId(), GetCurrentThreadId(), unique);
|
||||
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#pragma warning(disable : 4710)
|
||||
#pragma warning(push, 1)
|
||||
snprintf(name, sizeof(name) - 1,
|
||||
"\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
|
||||
GetCurrentProcessId(), GetCurrentThreadId(), unique);
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
*rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped,
|
||||
pipeTypeByte | pipeWait, 1, 4096, 4096, 0,
|
||||
(LPSECURITY_ATTRIBUTES)&saAttr);
|
||||
|
||||
if (invalidHandleValue == rd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr,
|
||||
openExisting, fileAttributeNormal, 0);
|
||||
|
||||
if (invalidHandleValue == wr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int subprocess_create(const char *const commandLine[], int options,
|
||||
struct subprocess_s *const out_process) {
|
||||
#if defined(_MSC_VER)
|
||||
int fd;
|
||||
void *rd, *wr;
|
||||
char *commandLineCombined;
|
||||
subprocess_size_t len;
|
||||
int i, j;
|
||||
const unsigned long startFUseStdHandles = 0x00000100;
|
||||
const unsigned long handleFlagInherit = 0x00000001;
|
||||
struct subprocess_subprocess_information_s processInfo;
|
||||
struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
|
||||
char *environment = 0;
|
||||
struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
startInfo.cb = sizeof(startInfo);
|
||||
startInfo.dwFlags = startFUseStdHandles;
|
||||
|
||||
if (subprocess_option_inherit_environment !=
|
||||
(options & subprocess_option_inherit_environment)) {
|
||||
environment = "\0\0";
|
||||
}
|
||||
|
||||
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = _open_osfhandle((subprocess_intptr_t)wr, 0);
|
||||
|
||||
if (-1 != fd) {
|
||||
out_process->stdin_file = _fdopen(fd, "wb");
|
||||
|
||||
if (0 == out_process->stdin_file) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
startInfo.hStdInput = rd;
|
||||
|
||||
if (options & subprocess_option_enable_async) {
|
||||
if (subprocess_create_named_pipe_helper(&rd, &wr)) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = _open_osfhandle((subprocess_intptr_t)rd, 0);
|
||||
|
||||
if (-1 != fd) {
|
||||
out_process->stdout_file = _fdopen(fd, "rb");
|
||||
|
||||
if (0 == out_process->stdout_file) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
startInfo.hStdOutput = wr;
|
||||
|
||||
if (subprocess_option_combined_stdout_stderr ==
|
||||
(options & subprocess_option_combined_stdout_stderr)) {
|
||||
out_process->stderr_file = out_process->stdout_file;
|
||||
startInfo.hStdError = startInfo.hStdOutput;
|
||||
} else {
|
||||
if (options & subprocess_option_enable_async) {
|
||||
if (subprocess_create_named_pipe_helper(&rd, &wr)) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = _open_osfhandle((subprocess_intptr_t)rd, 0);
|
||||
|
||||
if (-1 != fd) {
|
||||
out_process->stderr_file = _fdopen(fd, "rb");
|
||||
|
||||
if (0 == out_process->stderr_file) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
startInfo.hStdError = wr;
|
||||
}
|
||||
|
||||
if (options & subprocess_option_enable_async) {
|
||||
out_process->hEventOutput =
|
||||
CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0);
|
||||
out_process->hEventError =
|
||||
CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0);
|
||||
} else {
|
||||
out_process->hEventOutput = 0;
|
||||
out_process->hEventError = 0;
|
||||
}
|
||||
|
||||
// Combine commandLine together into a single string
|
||||
len = 0;
|
||||
for (i = 0; commandLine[i]; i++) {
|
||||
// For the ' ' and two '"' between items and trailing '\0'
|
||||
len += 3;
|
||||
|
||||
for (j = 0; '\0' != commandLine[i][j]; j++) {
|
||||
switch (commandLine[i][j]) {
|
||||
case '\\':
|
||||
if (commandLine[i][j + 1] != '"') break;
|
||||
case '"':
|
||||
len++;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
commandLineCombined = (char *)_alloca(len);
|
||||
|
||||
if (!commandLineCombined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Gonna re-use len to store the write index into commandLineCombined
|
||||
len = 0;
|
||||
|
||||
for (i = 0; commandLine[i]; i++) {
|
||||
if (0 != i) {
|
||||
commandLineCombined[len++] = ' ';
|
||||
}
|
||||
commandLineCombined[len++] = '"';
|
||||
|
||||
for (j = 0; '\0' != commandLine[i][j]; j++) {
|
||||
switch (commandLine[i][j]) {
|
||||
case '\\':
|
||||
if (commandLine[i][j + 1] != '"') break;
|
||||
case '"':
|
||||
commandLineCombined[len++] = '\\';
|
||||
}
|
||||
commandLineCombined[len++] = commandLine[i][j];
|
||||
}
|
||||
commandLineCombined[len++] = '"';
|
||||
}
|
||||
|
||||
commandLineCombined[len] = '\0';
|
||||
|
||||
if (!CreateProcessA(NULL,
|
||||
commandLineCombined, // command line
|
||||
NULL, // process security attributes
|
||||
NULL, // primary thread security attributes
|
||||
1, // handles are inherited
|
||||
0, // creation flags
|
||||
environment, // use parent's environment
|
||||
NULL, // use parent's current directory
|
||||
(LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer
|
||||
(LPPROCESS_INFORMATION)&processInfo)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
out_process->hProcess = processInfo.hProcess;
|
||||
|
||||
out_process->hStdInput = startInfo.hStdInput;
|
||||
|
||||
// We don't need the handle of the primary thread in the called process.
|
||||
CloseHandle(processInfo.hThread);
|
||||
|
||||
if (0 != startInfo.hStdOutput) {
|
||||
CloseHandle(startInfo.hStdOutput);
|
||||
|
||||
if (startInfo.hStdError != startInfo.hStdOutput) {
|
||||
CloseHandle(startInfo.hStdError);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
int stdinfd[2];
|
||||
int stdoutfd[2];
|
||||
int stderrfd[2];
|
||||
pid_t child;
|
||||
|
||||
if (0 != pipe(stdinfd)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 != pipe(stdoutfd)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (subprocess_option_combined_stdout_stderr !=
|
||||
(options & subprocess_option_combined_stdout_stderr)) {
|
||||
if (0 != pipe(stderrfd)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
child = fork();
|
||||
|
||||
if (-1 == child) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (0 == child) {
|
||||
// Close the stdin write end
|
||||
close(stdinfd[1]);
|
||||
// Map the read end to stdin
|
||||
dup2(stdinfd[0], STDIN_FILENO);
|
||||
|
||||
// Close the stdout read end
|
||||
close(stdoutfd[0]);
|
||||
// Map the write end to stdout
|
||||
dup2(stdoutfd[1], STDOUT_FILENO);
|
||||
|
||||
if (subprocess_option_combined_stdout_stderr ==
|
||||
(options & subprocess_option_combined_stdout_stderr)) {
|
||||
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||||
} else {
|
||||
// Close the stderr read end
|
||||
close(stderrfd[0]);
|
||||
// Map the write end to stdout
|
||||
dup2(stderrfd[1], STDERR_FILENO);
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcast-qual"
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
if (subprocess_option_inherit_environment !=
|
||||
(options & subprocess_option_inherit_environment)) {
|
||||
char *const environment[1] = {0};
|
||||
exit(execve(commandLine[0], (char *const *)commandLine, environment));
|
||||
} else {
|
||||
exit(execvp(commandLine[0], (char *const *)commandLine));
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
} else {
|
||||
// Close the stdin read end
|
||||
close(stdinfd[0]);
|
||||
// Store the stdin write end
|
||||
out_process->stdin_file = fdopen(stdinfd[1], "wb");
|
||||
|
||||
// Close the stdout write end
|
||||
close(stdoutfd[1]);
|
||||
// Store the stdout read end
|
||||
out_process->stdout_file = fdopen(stdoutfd[0], "rb");
|
||||
|
||||
if (subprocess_option_combined_stdout_stderr ==
|
||||
(options & subprocess_option_combined_stdout_stderr)) {
|
||||
out_process->stderr_file = out_process->stdout_file;
|
||||
} else {
|
||||
// Close the stderr write end
|
||||
close(stderrfd[1]);
|
||||
// Store the stderr read end
|
||||
out_process->stderr_file = fdopen(stderrfd[0], "rb");
|
||||
}
|
||||
|
||||
// Store the child's pid
|
||||
out_process->child = child;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FILE *subprocess_stdin(const struct subprocess_s *const process) {
|
||||
return process->stdin_file;
|
||||
}
|
||||
|
||||
FILE *subprocess_stdout(const struct subprocess_s *const process) {
|
||||
return process->stdout_file;
|
||||
}
|
||||
|
||||
FILE *subprocess_stderr(const struct subprocess_s *const process) {
|
||||
if (process->stdout_file != process->stderr_file) {
|
||||
return process->stderr_file;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int subprocess_join(struct subprocess_s *const process,
|
||||
int *const out_return_code) {
|
||||
#if defined(_MSC_VER)
|
||||
const unsigned long infinite = 0xFFFFFFFF;
|
||||
|
||||
if (0 != process->stdin_file) {
|
||||
fclose(process->stdin_file);
|
||||
process->stdin_file = 0;
|
||||
}
|
||||
if (0 != process->hStdInput) {
|
||||
CloseHandle(process->hStdInput);
|
||||
process->hStdInput = NULL;
|
||||
}
|
||||
|
||||
WaitForSingleObject(process->hProcess, infinite);
|
||||
|
||||
if (out_return_code) {
|
||||
if (!GetExitCodeProcess(process->hProcess,
|
||||
(unsigned long *)out_return_code)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
int status;
|
||||
|
||||
if (0 != process->stdin_file) {
|
||||
fclose(process->stdin_file);
|
||||
process->stdin_file = 0;
|
||||
}
|
||||
|
||||
if (process->child != waitpid(process->child, &status, 0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out_return_code) {
|
||||
if (WIFEXITED(status)) {
|
||||
*out_return_code = WEXITSTATUS(status);
|
||||
} else {
|
||||
*out_return_code = EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int subprocess_destroy(struct subprocess_s *const process) {
|
||||
if (0 != process->stdin_file) {
|
||||
fclose(process->stdin_file);
|
||||
process->stdin_file = 0;
|
||||
}
|
||||
|
||||
if (0 != process->stdout_file) {
|
||||
fclose(process->stdout_file);
|
||||
|
||||
if (process->stdout_file != process->stderr_file) {
|
||||
fclose(process->stderr_file);
|
||||
}
|
||||
|
||||
process->stdout_file = 0;
|
||||
process->stderr_file = 0;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
if (process->hProcess) {
|
||||
CloseHandle(process->hProcess);
|
||||
process->hProcess = 0;
|
||||
|
||||
if (0 != process->hStdInput) {
|
||||
CloseHandle(process->hStdInput);
|
||||
}
|
||||
|
||||
if (0 != process->hEventOutput) {
|
||||
CloseHandle(process->hEventOutput);
|
||||
}
|
||||
|
||||
if (0 != process->hEventError) {
|
||||
CloseHandle(process->hEventError);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int subprocess_terminate(struct subprocess_s *const process) {
|
||||
#if defined(_MSC_VER)
|
||||
unsigned int killed_process_exit_code;
|
||||
int success_terminate;
|
||||
int windows_call_result;
|
||||
|
||||
killed_process_exit_code = 99;
|
||||
windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code);
|
||||
success_terminate = (windows_call_result== 0) ? 1 : 0;
|
||||
return success_terminate;
|
||||
#else
|
||||
int result;
|
||||
result = kill(process->child, 9);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned subprocess_read_stdout(struct subprocess_s *const process,
|
||||
char *const buffer, unsigned size) {
|
||||
#if defined(_MSC_VER)
|
||||
void *handle;
|
||||
unsigned long bytes_read = 0;
|
||||
struct subprocess_overlapped_s overlapped = {0};
|
||||
overlapped.hEvent = process->hEventOutput;
|
||||
|
||||
handle = (void *)_get_osfhandle(_fileno(process->stdout_file));
|
||||
|
||||
if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) {
|
||||
const unsigned long errorIoPending = 997;
|
||||
unsigned long error = GetLastError();
|
||||
|
||||
// Means we've got an async read!
|
||||
if (error == errorIoPending) {
|
||||
if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read,
|
||||
1)) {
|
||||
const unsigned long errorIoIncomplete = 996;
|
||||
const unsigned long errorHandleEOF = 38;
|
||||
error = GetLastError();
|
||||
|
||||
if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (unsigned)bytes_read;
|
||||
#else
|
||||
const int fd = fileno(process->stdout_file);
|
||||
const ssize_t bytes_read = read(fd, buffer, size);
|
||||
|
||||
if (bytes_read < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return SUBPROCESS_CAST(unsigned, bytes_read);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned subprocess_read_stderr(struct subprocess_s *const process,
|
||||
char *const buffer, unsigned size) {
|
||||
#if defined(_MSC_VER)
|
||||
void *handle;
|
||||
unsigned long bytes_read = 0;
|
||||
struct subprocess_overlapped_s overlapped = {0};
|
||||
overlapped.hEvent = process->hEventError;
|
||||
|
||||
handle = (void *)_get_osfhandle(_fileno(process->stderr_file));
|
||||
|
||||
if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) {
|
||||
const unsigned long errorIoPending = 997;
|
||||
unsigned long error = GetLastError();
|
||||
|
||||
// Means we've got an async read!
|
||||
if (error == errorIoPending) {
|
||||
if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read,
|
||||
1)) {
|
||||
const unsigned long errorIoIncomplete = 996;
|
||||
const unsigned long errorHandleEOF = 38;
|
||||
error = GetLastError();
|
||||
|
||||
if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (unsigned)bytes_read;
|
||||
#else
|
||||
const int fd = fileno(process->stderr_file);
|
||||
const ssize_t bytes_read = read(fd, buffer, size);
|
||||
|
||||
if (bytes_read < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return SUBPROCESS_CAST(unsigned, bytes_read);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* SHEREDOM_SUBPROCESS_H_INCLUDED */
|
||||
Reference in New Issue
Block a user