codebrainz / geanypy

Python bindings for the Geany plugin API.
http://codebrainz.github.com/geanypy
GNU General Public License v2.0
45 stars 17 forks source link

Cannot call send_message with python string #32

Open scriptum opened 9 years ago

scriptum commented 9 years ago

I'm trying to write simple autocomplete plugin:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import geany, os, gettext, re, glob
from ctypes import *
gsc = geany.scintilla
gsc.WORDSTARTPOSITION = 2266
gsc.AUTOCSHOW = 2100
gsc.AUTOCACTIVE = 2102
class AutocompleteFilePlugin(geany.Plugin):

    __plugin_name__ = "Autocomplete file names"
    __plugin_description__ = ("Autocompletion based on existing files")
    __plugin_version__ = "0.1"
    __plugin_author__ = "Pavel Roschin <rpg89(at)post(dot)ru>"

    def editor_cb(self, obj, editor, nt):
        if nt.nmhdr.code != gsc.MODIFIED: return False
        if not nt.modification_type & (gsc.MOD_INSERT_TEXT | gsc.MOD_DELETE_TEXT): return False
        sci = editor.scintilla
        if sci.has_selection(): return False
        ssm = sci.send_message
        if ssm(gsc.AUTOCACTIVE): return False
        line = sci.get_current_line()
        start = sci.get_position_from_line(line)
        end = sci.get_current_position()
        if nt.modification_type & gsc.MOD_INSERT_TEXT: end += 1
        if start == end: return False
        col = sci.get_col_from_position(end)
        if col > 100: return False
        text = sci.get_contents_range(start, end)
        match = re.search('[^\s\'"<>()]+$', text)
        if not match: return False
        path = match.group(0)
        paths = glob.glob(path+'*')
        if len(paths) == 0: return False
        print "\n".join(paths)
        # ssm(gsc.AUTOCSHOW, len(path), "\n".join(paths))

    def __init__(self):
        geany.Plugin.__init__(self)
        geany.signals.connect("editor-notify", self.editor_cb)

Problematic line: ssm(gsc.AUTOCSHOW, len(path), "\n".join(paths)). Geany crashes if string passed to send_message function.

elextr commented 9 years ago

The third argument to send_message is expected to be a long (see here) not a char* and certainly not a Python string. This seems reasonable since Python doesn't use pointers anyway, so the sptr argument to Scintilla send_message is defined for those few places where a non-pointer argument is used.

You might get the Python string converted to char* and then to long using the appropriate ctypes functions and then pass that.

But note you can't pass a pointer in a long on windows 64, so its not portable anyway.

scriptum commented 9 years ago

Is it possible to add into geanypy another interface (maybe with same name?) which accepts python strings and does all casting inside C module?

I'm looking for any possible solution to call SCI_AUTOCSHOW from python.

elextr commented 9 years ago

It probably could be done send_text_message( message, otherarg, string) or similar.

codebrainz commented 9 years ago

Scintilla_send_message should probably use Scintilla's typedefs uptr_t and sptr_t or GLib's guintptr and gintptr (or std C's uintptr_t/intptr_t) rather than glong/long, in order to portably fit a pointer in it.

scriptum commented 9 years ago

Patch:

diff --git a/geanypy/src/geanypy-scintilla.c b/geanypy/src/geanypy-scintilla.c
index 9b3776d..8de6a22 100644
--- a/geanypy/src/geanypy-scintilla.c
+++ b/geanypy/src/geanypy-scintilla.c
@@ -783,6 +783,26 @@ Scintilla_send_message(Scintilla *self, PyObject *args, PyObject *kwargs)
 }

+static PyObject *
+Scintilla_send_text_message(Scintilla *self, PyObject *args, PyObject *kwargs)
+{
+   gint msg;
+   glong uptr = 0, ret;
+   const char *sptr = NULL;
+   static gchar *kwlist[] = { "msg", "lparam", "wparam", NULL };
+
+   SCI_RET_IF_FAIL(self);
+
+   if (PyArg_ParseTupleAndKeywords(args, kwargs, "i|lz", kwlist, &msg, &uptr, &sptr))
+   {
+       ret = scintilla_send_message(self->sci, msg, uptr, sptr);
+       return Py_BuildValue("l", ret);
+   }
+
+   Py_RETURN_NONE;
+}
+
+
 static PyMethodDef Scintilla_methods[] = {
    { "delete_marker_at_line", (PyCFunction) Scintilla_delete_marker_at_line, METH_KEYWORDS,
        "Deletes a line marker." },
@@ -877,6 +897,8 @@ static PyMethodDef Scintilla_methods[] = {
        "Begins grouping a set of edits together as one Undo action." },
    { "send_message", (PyCFunction) Scintilla_send_message, METH_KEYWORDS,
        "Send a message to the Scintilla widget." },
+   { "send_text_message", (PyCFunction) Scintilla_send_text_message, METH_KEYWORDS,
+       "Send a text message to the Scintilla widget." },
    { NULL }
 };
codebrainz commented 9 years ago

For the z format, I'm not sure if Scintilla handles NULL arguments. I've had sometimes feeding bad args to Scintilla and it just crashes. I guess if you tested some string functions and it works, it's ok, otherwise we could do like sptr?sptr:"" or whatever.

scriptum commented 9 years ago

I tested on this:

sci.send_text_message(gsc.AUTOCSHOW, 0, None)

Works well.

kugel- commented 8 years ago

I faced the scintilla_sendmessage() problem with peasy as well. I decided to not do anything about it for now (you'd need to provide a wrapper for every arg combination) but instead just rely on all necessary functionality being wrapped by Geany' sci* functions which can be called (and may add value).