Abdullah Diab’s Blog

Notepad++ Plugin To Run Python Scripts

I use Python everyday, it has become my first tool to use when I need to do anything. In Arabic (in Syrian Arabic specifically) I’d say that Python has become my hand and leg 😛

Yesterday I was writing a small Python script to read the YACC file and generate a list of all the specified rules inside it, so I don’t have to scroll through the long file to find out what rules are inside it 😉

I use Notepad++ as my default text editor on Windows, and I was writing the script using it – Notepad++. I wanted to test if the script is working, so I ran an instance of Command Line Prompt, and as I was going to change the directory to the directory of the script I thought; “Why doesn’t Notepad++ have a Run In Python command in it?”. So as usual I got pissed off and decided to create my own plugin to have that command in Notepad++ 8)

The Reference

I searched on the net, I found this Notepad++ Plugin Template, I edited it a little bit and I managed to do it 🙂

You can read below about the process and how it works, but if you just came here to download it click here.

The Process

To run a Python script; the plugin must do the following:

  • Get the path of the selected file in Notepad++.
  • Get the path of Python executable file, since not everyone has Python in the PATH environment variable.
  • Building the run command.
  • Execute the run command.

Get The Path of The Selected File In Notepad++

I also searched the net and found the following code:

std::wstring getCurrentFile(bool &ok)
{
	LRESULT result = ::SendMessage(nppData._nppHandle, NPPM_SAVECURRENTFILE, 0, 0);
	TCHAR path[MAX_PATH];
	::SendMessage(nppData._nppHandle, NPPM_GETFULLCURRENTPATH, MAX_PATH, (LPARAM)path);
	std::wstring wPath(path);
	if (result == 0) {
		ok = false;
		for (int i = 0; i < wPath.length() && !ok; i++)
			if (wPath[i] == '\\')
				ok = true;
	}
	else
		ok = true;
	return wPath;
}

The function sends a message to Notepad++ asking it to save the file before trying to run it, the result of the message will be either 1 which means that the file was saved, or 0 which means that the file wasn’t.

Then the function sends a message to Notepad++ asking it to put the full path of the currently selected file into a specific variable.

Now a little problem occurs when the file is unmodified, and the problem is that the first message will return 0 which means that the file wasn’t saved, while the second message will return a valid file path. So the function will search the path for a backslash `` which is the path separator in Windows, because a valid full path must contain at least one separator.

When the path is valid and the file is saved the function returns the path and sets the `ok` parameter to true, while in other cases it will return the path and set `ok` parameter to false.

Get The Path Of Python Executable File

The plugin will search for Python in its default path (“C:\Python[VER]”). If this method fails the plugin will search in registry for the key.

bool pythonExists(std::wstring foldername) {
	std::wstring path = L"C:\\";
	path += foldername;
	path += L"\\python.exe";
	WIN32_FIND_DATA data;
	HANDLE h = FindFirstFile(const_cast<LPCWSTR>(path.c_str()), &data);
	return (h != INVALID_HANDLE_VALUE);
}

	//This code will be run before the plugin calls the function 'getPythonLocation'
	std::wstring pythonLoc = L"";
	bool pythonInstalled = false;

	WIN32_FIND_DATA data;
	HANDLE h = FindFirstFile(L"c:\\python*", &data);
	if( h != INVALID_HANDLE_VALUE ) 
	{
		do 
		{
			if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
				continue;

			char*   nPtr = new char [lstrlen( data.cFileName ) + 1];
			for( int i = 0; i < lstrlen( data.cFileName ); i++ )
				nPtr[i] = char( data.cFileName[i] );

			nPtr[lstrlen( data.cFileName )] = '\0';
			if (pythonExists(const_cast<LPWSTR>(data.cFileName)))
			{
				pythonLoc = L"C:\\";
				pythonLoc += const_cast<LPWSTR>(data.cFileName);
				pythonLoc += L"\\";
				pythonInstalled = true;
				break;
			}
		} while (FindNextFile(h, &data));
	}

The Python installation path exists in the Windows Registry, so I wrote a small function to search the registry for the path and return it:

bool getPythonLocationFromRegistry(std::wstring &loc, HKEY baseKey) {
{
	HKEY hKey;
	if(RegOpenKeyEx(baseKey, TEXT("Software\\Python\\PythonCore"), 0,
		KEY_READ, &hKey) != ERROR_SUCCESS)
	{
		return false;
	}
	DWORD dwIdx=0;
	TCHAR szKeyName[1024];
	DWORD dwSize=1024;
	FILETIME fTime;
	TCHAR pyPath[MAX_PATH];
	DWORD length = MAX_PATH;

	if (RegEnumKeyEx(hKey, dwIdx, szKeyName, &dwSize, NULL, NULL, NULL, &fTime) == ERROR_SUCCESS)
	{
		HKEY hSubKey;
		if (RegOpenKeyEx(hKey, szKeyName, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS)
		{
			LONG x = RegOpenKeyEx(hSubKey, TEXT("InstallPath"), 0, KEY_READ, &hSubKey);
			if (x == ERROR_SUCCESS)
			{
				if(RegQueryValueEx(
					hSubKey,
					TEXT(""),
					NULL,
					NULL,
					(LPBYTE)pyPath,
					&length) != ERROR_SUCCESS)
				{
					return false;
				}

				loc = loc.append(pyPath);
				return true;
			}
			else
				return false;
		}
		else
			return false;
	}
	else
		return false;
}

The code above accepts the root key as a parameter, and the code that calls it will call it twice, once with HKEY_CURRENT_USER and the other with HKEY_LOCAL_MACHINE, so it will try to find it in both locations HKEY_CURRENT_USER\Software\Python\PythonCore and HKEY_LOCAL_MACHINE\Software\Python\PythonCore.

Building The Run Command

The plugin allows the user to choose to run the file in interactive mode, so the window won’t disappear after the program is done. I’ve also added another thing, that is the ability to run the file in pythonw.exe instead of python.exe, and that’s for applications with GUI, so you don’t have to see a console window behind your application GUI 😉

This one would be very simple, just concatenate: Python installation path, “python.exe” and the full path of the current file.

std::wstring buildRunCommand(std::wstring &filePath, std::wstring &pypath, bool isW = false, bool isI = false)
{
	std::wstring command = pypath;
	command += TEXT("python");
	if (isW)
		command += TEXT("w");
	command += TEXT(".exe ");
	if (isI)
		command += TEXT("-i ");
	command += TEXT("\"");
	command += filePath;
	command += TEXT("\"");
	return command;
}

Execute The Run Command

This function accepts a path parameter so it will run the process in the specified path (which is the path of the folder that includes the Python file to run) instead of the default path, this allows applications reading/writing files inside the same folder (or a relative path) of the application path to find those files.

This one is also simple, just create a new process with the run command.

bool launchPython(std::wstring &command, std::wstring &path)
{
	STARTUPINFOW si;
	PROCESS_INFORMATION pi;
	memset(&si, 0, sizeof(si));
	memset(&pi, 0, sizeof(pi));
	si.cb = sizeof(si);

	return CreateProcess(
		NULL,
		const_cast<LPWSTR>(command.c_str()),
		NULL,
		NULL,
		FALSE,
		CREATE_DEFAULT_ERROR_MODE,
		NULL,
		path.c_str(),
		&si,
		&pi) != 0;
}

Screenshot

PyNPP Notepad++ Plugin Screenshot

License

This work is licensed under GNU General Public License v3.

Download

PyNPP v1.2 on GitHub

GitHub

Plugin DLL:

Plugin DLL

Source Code

Source Code


Update History

  • 18, April 2010: PyNPP will search for Python in its default path C:\Python[VER]\.
  • 13, December 2010: PyNPP will allow the user to run the file in three modes: normal, interactive and GUI. As suggested by Andrew here.
  • 13, December 2010: PyNPP will run the file in its folder, so it can read/write files next to it. As suggested by Andrew here.
  • 03, January 2011: PyNPP will search for Python in both HKEY_CURRENT_USER\Software\Python\PythonCore and HKEY_LOCAL_MACHINE\Software\Python\PythonCore.
  • 05, January 2011: PyNPP will save the file before trying to run it.
    Fixed a bug in setting the current folder while running the process.
  • 12, August 2011: PyNPP allows you to select the Python folder using an options dialog. As suggested by Hönes here.
    • 21, December 2011: PyNPP moved to GitHub.
    • 27, April 2014: v1.1 released, PyNPP kills the Python instance it launches when the file is re-run. As suggested by Michał Kłopot.
    • 30, March 2015: v1.2 released, PyNPP supports debugging the file with PDB. As suggested by fdufnews.