1
# -*- coding: utf-8 -*-
3
# Copyright (c) 2007 The PIDA Project
5
#Permission is hereby granted, free of charge, to any person obtaining a copy
6
#of this software and associated documentation files (the "Software"), to deal
7
#in the Software without restriction, including without limitation the rights
8
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
#copies of the Software, and to permit persons to whom the Software is
10
#furnished to do so, subject to the following conditions:
12
#The above copyright notice and this permission notice shall be included in
13
#all copies or substantial portions of the Software.
15
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
"""The Emacs editor core classes for Pida.
25
This module and other Pida Emacs related classes are based on preliminary works
26
by Ali Afshar (see Emacs module in Pida 0.2.2).
28
The Emacs editor for Pida is also, heavily based on the Vim editor.
38
from pida.ui.views import PidaView
40
from pida.core.log import build_logger
41
from pida.core.editors import EditorService, _
44
from pida.utils.emacs.emacsembed import EmacsEmbedWidget
45
from pida.utils.emacs.emacscom import EmacsClient, EmacsServer, EMACS_SCRIPT
48
class EmacsView(PidaView):
51
self._emacs = EmacsEmbedWidget('emacs', self.svc.script_path)
52
self.add_main_widget(self._emacs)
57
def grab_input_focus(self):
58
self._emacs.grab_input_focus()
61
class EmacsCallback(object):
62
"""Emacs editor callback behaviours.
64
Communication with Emacs process is handled by EmacsClient in the pIDA->Emacs
65
way, and EmacsServer the other way. On occurence of a message, EmacsServer
66
extracts a request name and arguments, and then tries to invoke the matching
67
method on the EmacsCallback object.
69
Callbacks' names are built with the Emacs message names, prefixed with 'cb_'.
70
Each callback accepts exactly one argument.
73
def __init__(self, svc):
75
self._log = logging.getLogger('emacs')
77
self._server = EmacsServer(self)
80
"""Establish the link with Emacs."""
81
return self._server.connect()
83
def cb_pida_pong(self, foo):
84
"""Emacs response to a ping.
86
This message is used to test connection at startup.
88
self._log.debug('emacs ready')
89
self._svc.emit_editor_started()
92
def cb_window_configuration_change_hook(self, filename):
93
"""Buffer changed event.
95
Actually, this hook is called whenever the window containing the
96
buffer changes. So notification can occur only when window is resized
99
self._svc.top_buffer = filename
100
current = self._svc.current_document
101
if filename and (not current or current.filename != filename):
102
self._log.debug('emacs buffer changed "%s"' % filename)
103
if os.path.isdir(filename):
104
self._svc.boss.cmd('filemanager', 'browse', new_path=filename)
105
self._svc.boss.cmd('filemanager', 'present_view')
107
self._svc.boss.cmd('buffer', 'open_file', file_name=filename)
110
def cb_kill_buffer_hook(self, filename):
111
"""Buffer closed event."""
113
self._log.debug('emacs buffer killed "%s"' % filename)
114
self._svc.remove_file(filename)
115
self._svc.boss.get_service('buffer').cmd('close_file', file_name=filename)
118
def cb_find_file_hooks(self, filename):
119
"""File opened event."""
120
# Nothing to do here. The window configuration change hook will
121
# provide notification for the new buffer.
123
self._log.debug('emacs buffer opened "%s"' % filename)
126
def cb_after_save_hook(self, filename):
127
"""Buffer saved event."""
128
self._log.debug('emacs buffer saved "%s"' % filename)
129
self._svc.boss.cmd('buffer', 'current_file_saved')
132
def cb_kill_emacs_hook(self, foo):
133
"""Emacs killed event."""
134
self._log.debug('emacs killed')
135
self._svc.inactivate_client()
136
self._svc.boss.stop(force=True)
140
class Emacs(EditorService):
141
"""The Emacs service.
143
This service is the Emacs editor driver. Emacs instance creation is decided
144
there and orders for Emacs are sent to it which forwards them to the
145
EmacsClient instance.
148
def _create_initscript(self):
149
self.script_path = os.path.join(
150
self.boss.get_pida_home(), 'pida_emacs_init.el')
151
f = open(self.script_path, 'w')
152
f.write(EMACS_SCRIPT)
155
def emit_editor_started(self):
156
self.boss.get_service('editor').emit('started')
159
"""Start the editor"""
160
self._log = build_logger('emacs')
161
self._create_initscript()
164
# The current document. Its value is set by Pida and used to drop
165
# useless messages to emacs.
168
# The current buffer displayed. Its value is set by the EmacsCallback
169
# instance and is used as well to prevent sending useless messages.
170
self._top_buffer = ''
172
self._current_line = 1
173
self._cb = EmacsCallback(self)
174
self._client = EmacsClient()
175
self._view = EmacsView(self)
177
# Add the view to the top level window. Only after that, it will be
178
# possible to add a socket in the view.
179
self.boss.cmd('window', 'add_view', paned='Editor', view=self._view)
181
# Now create the socket and embed the Emacs window.
183
if self._cb.connect():
184
gobject.timeout_add(250, self._client.ping)
189
def _get_current_document(self):
192
def _set_current_document(self, document):
193
self._current = document
195
current_document = property(fget=_get_current_document,
196
fset=_set_current_document,
198
doc="The document currently edited")
200
def _get_top_buffer(self):
201
return self._top_buffer
203
def _set_top_buffer(self, filename):
204
self._top_buffer = filename
206
top_buffer = property(fget=_get_top_buffer,
207
fset=_set_top_buffer,
209
doc="The last buffer reported as being viewed by emacs")
211
def inactivate_client(self):
212
self._client.inactivate()
214
def open(self, document):
215
"""Open a document"""
216
if document is not self._current:
217
if self.top_buffer != document.filename:
218
if document.unique_id in self._documents:
219
self._client.change_buffer(document.filename)
221
self._client.open_file(document.filename)
222
self._documents[document.unique_id] = document
223
self.current_document = document
225
def open_many(documents):
226
"""Open a few documents"""
229
def close(self, document):
230
if document.unique_id in self._documents:
231
self._remove_document(document)
232
self._client.close_buffer(document.filename)
234
def remove_file(self, filename):
235
document = self._get_document_for_filename(filename)
236
if document is not None:
237
self._remove_document(document)
239
def _remove_document(self, document):
240
del self._documents[document.unique_id]
242
def _get_document_for_filename(self, filename):
243
for uid, doc in self._documents.iteritems():
244
if doc.filename == filename:
248
"""Close all the documents"""
251
"""Save the current document"""
252
self._client.save_buffer()
254
def save_as(self, filename):
255
"""Save the current document as another filename"""
259
"""Revert to the loaded version of the file"""
260
self._client.revert_buffer()
262
def goto_line(self, line):
264
self._client.goto_line(line + 1)
268
"""Cut to the clipboard"""
272
"""Copy to the clipboard"""
276
"""Paste from the clipboard"""
285
def grab_focus(self):
287
self._view.grab_input_focus()
289
def define_sign_type(self, name, icon, linehl, text, texthl):
293
def undefine_sign_type(self, name):
297
#def _add_sign(self, type, filename, line):
299
#def _del_sign(self, type, filename, line):
301
def show_sign(self, type, filename, line):
305
def hide_sign(self, type, filename, line):
309
def set_current_line(self, line_number):
310
self._current_line = line_number
312
def get_current_line(self):
313
return self._current_line
315
#def call_with_current_word(self, callback):
316
# return self._com.get_cword(self.server, callback)
318
#def call_with_selection(self, callback):
319
# return self._com.get_selection(self.server, callback)
321
def set_path(self, path):
322
return self._client.set_directory(path)
325
# Required Service attribute for service loading
329
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: