Skip to content

Commit 8371b03

Browse files
committed
Console.run working, but stdout is buffered
- not sure we can fix the buffering problem - may just need to leave it buffered. - need to improve the call so we don't need to give objects for stdout/err - stderr needs enabling
1 parent f61dee6 commit 8371b03

8 files changed

Lines changed: 291 additions & 6 deletions

File tree

PythonScript/project/PythonScript2010.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<ClCompile Include="..\src\MenuManager.cpp" />
123123
<ClCompile Include="..\src\NotepadPlusWrapper.cpp" />
124124
<ClCompile Include="..\src\NotepadPython.cpp" />
125+
<ClCompile Include="..\src\ProcessExecute.cpp" />
125126
<ClCompile Include="..\src\PromptDialog.cpp" />
126127
<ClCompile Include="..\src\PyProducerConsumer.cpp" />
127128
<ClCompile Include="..\src\PythonConsole.cpp" />
@@ -148,6 +149,7 @@
148149
<ClInclude Include="..\src\NotepadPlusBuffer.h" />
149150
<ClInclude Include="..\src\NotepadPlusWrapper.h" />
150151
<ClInclude Include="..\src\NotepadPython.h" />
152+
<ClInclude Include="..\src\ProcessExecute.h" />
151153
<ClInclude Include="..\src\PromptDialog.h" />
152154
<ClInclude Include="..\src\PyProducerConsumer.h" />
153155
<ClInclude Include="..\src\PythonConsole.h" />

PythonScript/project/PythonScript2010.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@
129129
<ClCompile Include="..\src\PromptDialog.cpp">
130130
<Filter>Source Files</Filter>
131131
</ClCompile>
132+
<ClCompile Include="..\src\ProcessExecute.cpp">
133+
<Filter>Source Files</Filter>
134+
</ClCompile>
132135
</ItemGroup>
133136
<ItemGroup>
134137
<ClInclude Include="..\src\AboutDialog.h">
@@ -206,6 +209,9 @@
206209
<ClInclude Include="..\include\PythonScript\NppPythonScript.h">
207210
<Filter>Header Files</Filter>
208211
</ClInclude>
212+
<ClInclude Include="..\src\ProcessExecute.h">
213+
<Filter>Header Files</Filter>
214+
</ClInclude>
209215
</ItemGroup>
210216
<ItemGroup>
211217
<ResourceCompile Include="..\src\PythonScript.rc">
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
#include "stdafx.h"
2+
#include "ProcessExecute.h"
3+
4+
/* The Console Redirection is taken from the TagsView plugin from Vitaliy Dovgan.
5+
* My thanks to him for pointing me in the right direction. :)
6+
* And of course for NppExec, without which, Notepad++ would only
7+
* be half as powerful.
8+
*/
9+
10+
#define DEFAULT_PIPE_SIZE 1
11+
#define PIPE_READBUFSIZE 4096
12+
13+
using namespace boost::python;
14+
15+
ProcessExecute::ProcessExecute()
16+
{
17+
}
18+
19+
ProcessExecute::~ProcessExecute()
20+
{
21+
}
22+
23+
24+
bool ProcessExecute::isWindowsNT()
25+
{
26+
OSVERSIONINFO osv;
27+
osv.dwOSVersionInfoSize = sizeof(osv);
28+
::GetVersionEx(&osv);
29+
return (osv.dwPlatformId >= VER_PLATFORM_WIN32_NT);
30+
}
31+
32+
int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin)
33+
{
34+
if (pyStdout.is_none())
35+
return 3;
36+
if (pyStderr.is_none())
37+
return 4;
38+
39+
// Create out, err, and in pipes (ignore in, initially)
40+
SECURITY_DESCRIPTOR sd;
41+
SECURITY_ATTRIBUTES sa;
42+
43+
Py_BEGIN_ALLOW_THREADS
44+
45+
if (isWindowsNT())
46+
{
47+
::InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION );
48+
::SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE );
49+
sa.lpSecurityDescriptor = &sd;
50+
}
51+
else
52+
{
53+
sa.lpSecurityDescriptor = NULL;
54+
}
55+
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
56+
sa.bInheritHandle = TRUE;
57+
58+
if (!::CreatePipe(&m_hStdOutReadPipe, &m_hStdOutWritePipe, &sa, DEFAULT_PIPE_SIZE))
59+
{
60+
// TODO throw exception
61+
return -1;
62+
}
63+
64+
if (!::CreatePipe(&m_hStdErrReadPipe, &m_hStdErrWritePipe, &sa, DEFAULT_PIPE_SIZE))
65+
{
66+
// TODO throw exception
67+
return -1;
68+
}
69+
70+
HANDLE stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
71+
DWORD dwThreadId;
72+
PipeReaderArgs stdoutReaderArgs;
73+
PipeReaderArgs stderrReaderArgs;
74+
75+
stdoutReaderArgs.processExecute = this;
76+
stdoutReaderArgs.hPipeRead = m_hStdOutReadPipe;
77+
stdoutReaderArgs.hPipeWrite = m_hStdOutWritePipe;
78+
stdoutReaderArgs.pythonFile = pyStdout;
79+
stdoutReaderArgs.stopEvent = stopEvent;
80+
stdoutReaderArgs.completedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
81+
stderrReaderArgs.processExecute = this;
82+
stderrReaderArgs.hPipeRead = m_hStdErrReadPipe;
83+
stderrReaderArgs.hPipeWrite = m_hStdErrWritePipe;
84+
stderrReaderArgs.stopEvent = stopEvent;
85+
stderrReaderArgs.pythonFile = pyStderr;
86+
stderrReaderArgs.completedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
87+
88+
// start thread functions for stdout and stderr
89+
HANDLE hStdoutThread = CreateThread(
90+
NULL, // no security attribute
91+
0, // default stack size
92+
pipeReader, // thread proc
93+
(LPVOID) &stdoutReaderArgs, // thread parameter
94+
0, // not suspended
95+
&dwThreadId); // returns thread ID
96+
/*
97+
HANDLE hStderrThread = CreateThread(
98+
NULL, // no security attribute
99+
0, // default stack size
100+
pipeReader, // thread proc
101+
(LPVOID) &stderrReaderArgs, // thread parameter
102+
0, // not suspended
103+
&dwThreadId); // returns thread ID
104+
*/
105+
106+
107+
// start process
108+
PROCESS_INFORMATION pi;
109+
STARTUPINFO si;
110+
111+
112+
::SetHandleInformation(m_hStdOutReadPipe, HANDLE_FLAG_INHERIT, 0);
113+
::SetHandleInformation(m_hStdErrReadPipe, HANDLE_FLAG_INHERIT, 0);
114+
115+
// initialize STARTUPINFO struct
116+
::ZeroMemory( &si, sizeof(STARTUPINFO) );
117+
si.cb = sizeof(STARTUPINFO);
118+
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
119+
si.wShowWindow = SW_HIDE;
120+
si.hStdInput = NULL;
121+
si.hStdOutput = m_hStdOutWritePipe;
122+
si.hStdError = m_hStdOutWritePipe;
123+
124+
::ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );
125+
int commandLineLength = _tcslen(commandLine) + 1;
126+
TCHAR *cmdLine = new TCHAR[commandLineLength];
127+
_tcscpy_s(cmdLine, commandLineLength, commandLine);
128+
129+
if ( ::CreateProcess(
130+
NULL,
131+
cmdLine,
132+
NULL, // security
133+
NULL, // security
134+
TRUE, // inherits handles
135+
CREATE_NEW_PROCESS_GROUP, // creation flags
136+
NULL, // environment
137+
NULL, // current directory
138+
&si, // startup info
139+
&pi // process info
140+
))
141+
{
142+
// wait for process to exit
143+
::WaitForSingleObject(pi.hProcess, INFINITE);
144+
145+
}
146+
147+
148+
// Abort / Stop somehow the reader threads
149+
SetEvent(stopEvent);
150+
HANDLE handles[] = { stdoutReaderArgs.completedEvent, stderrReaderArgs.completedEvent };
151+
152+
// WaitForMultipleObjects(2, handles, TRUE, INFINITE);
153+
int returnedIndex = WaitForSingleObject(stdoutReaderArgs.completedEvent, INFINITE);
154+
Py_END_ALLOW_THREADS
155+
return 0;
156+
}
157+
158+
159+
DWORD WINAPI ProcessExecute::pipeReader(void *args)
160+
{
161+
PipeReaderArgs* pipeReaderArgs = reinterpret_cast<PipeReaderArgs*>(args);
162+
163+
DWORD bytesRead;
164+
165+
166+
char buffer[PIPE_READBUFSIZE];
167+
BOOL success = TRUE;
168+
BOOL processFinished = FALSE;
169+
BOOL dataFinished = FALSE;
170+
OVERLAPPED oOverlap;
171+
oOverlap.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
172+
HANDLE handles[2];
173+
handles[0] = pipeReaderArgs->stopEvent;
174+
int handleIndex;
175+
176+
while(!dataFinished)
177+
{
178+
::PeekNamedPipe(pipeReaderArgs->hPipeRead, NULL, 0, NULL, &bytesRead, NULL);
179+
180+
if (processFinished && 0 == bytesRead)
181+
{
182+
dataFinished = TRUE;
183+
break;
184+
}
185+
186+
187+
188+
if (bytesRead > 0)
189+
{
190+
success = ReadFile(pipeReaderArgs->hPipeRead, buffer, PIPE_READBUFSIZE - 1, &bytesRead, NULL);
191+
buffer[bytesRead] = '\0';
192+
PyGILState_STATE gstate = PyGILState_Ensure();
193+
try
194+
{
195+
pipeReaderArgs->pythonFile.attr("write")(boost::python::str(const_cast<const char *>(buffer)));
196+
}
197+
catch(...)
198+
{
199+
PyErr_Print();
200+
}
201+
PyGILState_Release(gstate);
202+
//handleIndex = WaitForMultipleObjects(2, handles, FALSE, 100);
203+
}
204+
else
205+
{
206+
handleIndex = WaitForSingleObject(pipeReaderArgs->stopEvent, 100);
207+
}
208+
209+
switch(handleIndex)
210+
{
211+
case WAIT_OBJECT_0:
212+
// Process Stopped
213+
{
214+
processFinished = TRUE;
215+
}
216+
break;
217+
218+
219+
}
220+
}
221+
222+
CloseHandle(pipeReaderArgs->hPipeRead);
223+
CloseHandle(pipeReaderArgs->hPipeWrite);
224+
SetEvent(pipeReaderArgs->completedEvent);
225+
226+
return 0;
227+
}

PythonScript/src/ProcessExecute.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef _PROCESSEXECUTE_H
2+
#define _PROCESSEXECUTE_H
3+
4+
#include "stdafx.h"
5+
6+
class ProcessExecute
7+
{
8+
public:
9+
10+
ProcessExecute();
11+
~ProcessExecute();
12+
13+
int execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin);
14+
15+
protected:
16+
static bool isWindowsNT();
17+
18+
private:
19+
static DWORD WINAPI pipeReader(void *args);
20+
HANDLE m_hStdOutReadPipe;
21+
HANDLE m_hStdOutWritePipe;
22+
HANDLE m_hStdErrReadPipe;
23+
HANDLE m_hStdErrWritePipe;
24+
};
25+
26+
struct PipeReaderArgs
27+
{
28+
ProcessExecute* processExecute;
29+
HANDLE hPipeRead;
30+
HANDLE hPipeWrite;
31+
HANDLE stopEvent;
32+
HANDLE completedEvent;
33+
boost::python::object pythonFile;
34+
};
35+
36+
37+
#endif

PythonScript/src/PythonConsole.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11

22
#include "stdafx.h"
3-
3+
#include "WcharMbcsConverter.h"
44
#include "PythonConsole.h"
55
#include "ConsoleDialog.h"
66
#include "PythonHandler.h"
7+
#include "ProcessExecute.h"
78

9+
using namespace std;
810
using namespace boost::python;
911
using namespace NppPythonScript;
1012

@@ -98,6 +100,14 @@ void PythonConsole::stopStatement()
98100

99101
}
100102

103+
void PythonConsole::runCommand(str text, boost::python::object pyStdout, boost::python::object pyStderr)
104+
{
105+
ProcessExecute process;
106+
shared_ptr<TCHAR> cmdLine = WcharMbcsConverter::char2tchar(extract<const char *>(text));
107+
process.execute(cmdLine.get(), pyStdout, pyStderr, object());
108+
}
109+
110+
101111

102112
void PythonConsole::runStatement(const char *statement)
103113
{
@@ -174,6 +184,8 @@ void PythonConsole::stopStatementWorker(PythonConsole *console)
174184
void export_console()
175185
{
176186
class_<PythonConsole>("Console", no_init)
177-
.def("write", &PythonConsole::writeText, "Writes text to the console. Uses the __str__ function of the object passed.");
187+
.def("write", &PythonConsole::writeText, "Writes text to the console. Uses the __str__ function of the object passed.")
188+
.def("run", &PythonConsole::runCommand, "Runs a command on the console");
189+
178190

179191
}

PythonScript/src/PythonConsole.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class PythonConsole : public NppPythonScript::PyProducerConsumer<const char *>,
3434
bool runStatementWorker(const char *statement);
3535
virtual void consume(const char *statement);
3636

37+
void runCommand(boost::python::str text, boost::python::object pyStdout, boost::python::object pyStderr);
3738

3839
HWND getScintillaHwnd() { return mp_consoleDlg->getScintillaHwnd(); };
3940

PythonScript/src/PythonScriptVersion.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
#include "stdafx.h"
66
#endif
77

8-
#define PYSCR_VERSION_NUMERIC 0,6,0,0
9-
#define PYSCR_VERSION_STRING "0.6.0.0"
8+
#define PYSCR_VERSION_NUMERIC 0,6,1,0
9+
#define PYSCR_VERSION_STRING "0.6.1.0"
1010

1111

1212

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
# built documents.
4949
#
5050
# The short X.Y version.
51-
version = '0.6'
51+
version = '0.6.1'
5252
# The full version, including alpha/beta/rc tags.
53-
release = '0.6'
53+
release = '0.6.1'
5454

5555
# The language for content autogenerated by Sphinx. Refer to documentation
5656
# for a list of supported languages.

0 commit comments

Comments
 (0)