Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Automated merge with http://hg.mozilla.org/labs/bespin

  • Loading branch information...
commit 0385327f558879dbb11dcc85e44f463a0c93cb95 1 parent 5a71e36
@bespin authored shoe committed
Showing with 990 additions and 357 deletions.
  1. +3 −0  .hgtags
  2. +14 −7 README.txt
  3. +1 −1  backend/python/bespin/__init__.py
  4. +6 −1 backend/python/bespin/controllers.py
  5. +2 −2 backend/python/bespin/model.py
  6. +4 −3 backend/python/pavement.py
  7. +1 −1  backend/python/production/pavement.py
  8. +2 −0  backend/python/setup.py
  9. +387 −0 docs/events.html
  10. +72 −52 docs/index.html
  11. +1 −1  frontend/dashboard.html
  12. +2 −2 frontend/editor.html
  13. +6 −2 frontend/index.html
  14. +1 −1  frontend/js/bespin/bespin.js
  15. +2 −8 frontend/js/bespin/bootstrap.js
  16. +1 −1  frontend/js/bespin/client/filesystem.js
  17. +1 −0  frontend/js/bespin/client/server.js
  18. +8 −3 frontend/js/bespin/client/session.js
  19. +1 −1  frontend/js/bespin/client/settings.js
  20. +19 −2 frontend/js/bespin/cmd/commandline.js
  21. +15 −3 frontend/js/bespin/cmd/commands.js
  22. +1 −3 frontend/js/bespin/dashboard/components.js
  23. +113 −69 frontend/js/bespin/dashboard/dashboard.js
  24. +1 −0  frontend/js/bespin/dashboard/dependencies.js
  25. +46 −47 frontend/js/bespin/editor/actions.js
  26. +49 −14 frontend/js/bespin/editor/editor.js
  27. +26 −22 frontend/js/bespin/editor/events.js
  28. +72 −3 frontend/js/bespin/editor/model.js
  29. +51 −47 frontend/js/bespin/events.js
  30. +5 −7 frontend/js/bespin/syntax/syntax.js
  31. +4 −3 frontend/js/bespin/user/utils.js
  32. +20 −22 frontend/js/bespin/util/clipboard.js
  33. +6 −3 frontend/js/bespin/util/navigate.js
  34. +13 −2 frontend/js/bespin/util/util.js
  35. +28 −16 frontend/js/th/components.js
  36. +3 −6 frontend/js/th/th.js
  37. +3 −2 pavement.py
View
3  .hgtags
@@ -1,2 +1,5 @@
30b7a1db05072c555ec7930ae6a84c1f31f26ae3 0.1.3
4f73e9e89e81e3f8a3c1ecf28e5e04d0bf63eeb5 0.1.3
+78adea9fb4b7e33df220817a48a13fab36258861 0.1.4
+d44a4512c0022bae23a36a3d3a5ac4a701f41156 0.1.5
+1fcf4e024671c5c62caed100ac8f788a90eb2886 0.1.6
View
21 README.txt
@@ -7,7 +7,6 @@ editor using HTML 5 technology.
Project home page: http://labs.mozilla.com/projects/bespin/
Live system: https://bespin.mozilla.com/
-
Thanks for downloading the code to the Bespin project. You can easily get
Bespin's Python server running on your local Mac or Linux machine (see note
about Windows below).
@@ -48,8 +47,8 @@ You can run the unit tests by running::
Updating the Required Files
---------------------------
-If the "requirements.txt" file changes, you can re-install the required packages
-by running::
+If the "requirements.txt" file changes, you can re-install the
+required packages by running::
paver required
@@ -57,6 +56,12 @@ You can also force upgrade all of the packages like so::
pip install -U -r requirements.txt
+More Documentation
+------------------
+
+Documentation for Bespin's code and APIs are actually part of every
+instance of the Bespin server. To view the docs on your local instance, just
+browse to http://127.0.0.1:8080/docs/.
Contributing to Bespin
----------------------
@@ -67,12 +72,14 @@ For details see:
The source repository is in Mercurial at:
http://hg.mozilla.org/labs/bespin/
-
Note about running on Windows
-----------------------------
-The current, up-to-date Bespin backend is written in Python. Because Python is cross-platform, it should be possible (and likely not too difficult) to make the backend work on Windows once Python 2.5 is installed. However, this has not been tested and there are likely two issues:
+The current, up-to-date Bespin backend is written in Python. Because
+Python is cross-platform, it should be possible (and likely not too
+difficult) to make the backend work on Windows once Python 2.5 is
+installed. However, this has not been tested and there are likely two
+issues:
1. some libraries used by Bespin try to compile C code
-2. some paths may not be correct on Windows systems
-
+2. some paths may not be correct on Windows systems
View
2  backend/python/bespin/__init__.py
@@ -28,6 +28,6 @@
# BEGIN VERSION BLOCK
VERSION = 'tip'
-VERSION_NAME = '(none)'
+VERSION_NAME = 'DEVELOPMENT MODE'
API_VERSION = 'dev'
# END VERSION BLOCK
View
7 backend/python/bespin/controllers.py
@@ -344,12 +344,17 @@ def _perform_import(file_manager, user, project_name, filename, fileobj):
func(user,
project, filename, fileobj)
return
+
+def validate_url(url):
+ if not url.startswith("http://") and not url.startswith("https://"):
+ raise BadRequest("Invalid url: " + url)
+ return url
@expose(r'^/project/fromurl/(?P<project_name>[^/]+)', "POST")
def import_from_url(request, response):
project_name = request.kwargs['project_name']
- url = request.body
+ url = validate_url(request.body)
try:
resp = httplib2.Http().request(url, method="HEAD")
except httplib2.HttpLib2Error, e:
View
4 backend/python/bespin/model.py
@@ -57,8 +57,8 @@
Base = declarative_base()
-# quotas are expressed in 1 million byte increments
-QUOTA_UNITS = 1000000
+# quotas are expressed in 1 megabyte increments
+QUOTA_UNITS = 1048576
class ConflictError(Exception):
pass
View
7 backend/python/pavement.py
@@ -32,7 +32,10 @@
from setuptools import find_packages
from paver.setuputils import find_package_data
-from paver.defaults import *
+from paver.easy import *
+from paver import setuputils
+setuputils.install_distutils_tasks()
+
execfile(os.path.join('bespin', '__init__.py'))
@@ -85,8 +88,6 @@ def production():
non_production_packages.add(name)
external_libs.append("libs/%s" % (f.basename()))
- sh("../../bin/pip freeze -r ../../requirements.txt %s" % (production_requirements))
-
lines = production_requirements.lines()
requirement_pattern = re.compile(r'^(.*)==')
View
2  backend/python/production/pavement.py
@@ -1,4 +1,4 @@
-from paver.defaults import *
+from paver.easy import *
import paver.virtual
View
2  backend/python/setup.py
@@ -1,5 +1,7 @@
# Don't run this. This is just for pip.
+from paver import tasks
+tasks.environment = tasks.Environment()
import pavement
from setuptools import setup
View
387 docs/events.html
@@ -0,0 +1,387 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <link rel="stylesheet" type="text/css" href="code-illuminated/docs.css" />
+ <title>Bespin Documentation: Events</title>
+</head>
+<body>
+<div id="content">
+<div id="overview" class="documentation">
+
+ <h1>Bespin</h1>
+ <p><b>Overview</b></p>
+
+ <p>Our goal is to have a loosely coupled API that makes it as easy for people to extend and work with Bespin. One of the ways we are accomplish this is by having a simple publish/subscribe structure to work with.</p>
+
+ <p>This page will attempt to clearly define the various events. You may of course create your own events. If you do so, we hope that you follow a convention such as: <code>bespin:plugin:[nameofplugin]:[event][optional:subevent]</code>, e.g. <code>bespin:svn:checkout</code>.</p>
+
+ <p>Some of the core events are configured in <a href="#code/bespin/events.js">the bespin.events.* module</a>. Then others go into the module specific events. It is recommended to place events into the scope of a module where possible. You will notice this pattern frequently in our code, where we register a FooEvents object, give it a scope, and it can then wire up the events there and then as it has access to the objects needed... in scope.</p>
+
+ <h1 style="border-bottom: 1px solid #ccc">Directory Events</h1>
+
+ <h2>bespin:directory:create</h2>
+ <p>
+ Create a new directory.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project, or use the session scope</li>
+ <li><code>path</code>: Path, or empty (danger!)</li>
+ </ul>
+ </p>
+
+ <h2>bespin:directory:delete</h2>
+ <p>
+ Delete a directory.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project, or use the session scope</li>
+ <li><code>path</code>: Path, or empty (danger!)</li>
+ </ul>
+ </p>
+
+
+ <h1 style="border-bottom: 1px solid #ccc">Command and commandline Events</h1>
+
+ <p><code>bespin.cmd.commandline.Events</code> houses the various events for the command line.</p>
+
+ <h2>bespin:cmdline:showinfo</h2>
+ <p>
+ Observe when others want to show data in the info bar for the command line.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>msg</code>: The message to display in the info bar</li>
+ <li><code>autohide</code>: Optionally, hide the info bar after a short period</li>
+ </ul>
+ </p>
+
+ <h2>bespin:cmdline:suppressinfo</h2>
+ <p>
+ Tell the info bar to be quiet until we tell it to unsuppress (below).
+ </p>
+
+ <h2>bespin:cmdline:unsuppressinfo</h2>
+ <p>
+ The info bar can start talking again, until someone asks it to suppress.
+ </p>
+
+ <h2>bespin:cmdline:executed</h2>
+ <p>
+ A given command has been executed.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>commandname</code>: Name of the command run</li>
+ <li><code>args</code>: arguments passed into the command</li>
+ </ul>
+ </p>
+
+ <h2>bespin:cmdline:execute</h2>
+ <p>
+ Go ahead and execute a command.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>command</code>: Name of the command to run</li>
+ <li><code>args</code>: arguments to be passed to the command, just as you would on the command line</li>
+ </ul>
+ </p>
+
+ <h2>bespin:commands:load</h2>
+ <p>
+ Create a new command in your special command directory.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>commandname</code>: Name of the command run</li>
+ </ul>
+ </p>
+
+ <h2>bespin:commands:edit</h2>
+ <p>
+ Edit the given command.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>commandname</code>: Name of the command run</li>
+ </ul>
+ </p>
+
+ <h2>bespin:commands:list</h2>
+ <p>
+ List the custom commands that a user has.
+ </p>
+
+ <h2>bespin:commands:delete</h2>
+ <p>
+ Delete the given command.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>commandname</code>: Name of the command run</li>
+ </ul>
+ </p>
+
+
+ <h1 style="border-bottom: 1px solid #ccc">Project Events</h1>
+
+ <h2>bespin:project:create</h2>
+ <p>
+ Create a new project.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the new project to be created</li>
+ </ul>
+ </p>
+
+ <h2>bespin:project:delete</h2>
+ <p>
+ Delete an existing project.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to be deleted</li>
+ </ul>
+ </p>
+
+ <h2>bespin:project:rename</h2>
+ <p>
+ Rename an existing project.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>currentProject</code>: Name of the existing project to be renamed</li>
+ <li><code>newProject</code>: The new name for the project</li>
+ </ul>
+ </p>
+
+ <h2>bespin:project:import</h2>
+ <p>
+ Import a project.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to be imported (can be new or existing)</li>
+ <li><code>url</code>: Path to the archive for the project</li>
+ </ul>
+ </p>
+
+ <h2>bespin:project:set</h2>
+ <p>
+ The project being looked at has changed (stored in the session scope).
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: The project that now has scope</li>
+ </ul>
+ </p>
+
+
+ <h1 style="border-bottom: 1px solid #ccc">Editor Events</h1>
+
+ <p></p>
+
+ <h2>bespin:editor:titlechange</h2>
+ <p>
+ Observe a title change event and then... change the document.title!
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>title</code>: Optionally, the title to set</li>
+ <li><code>filename</code>: Optionally, a filename, which then turns on the "edit mode title"</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:preview</h2>
+ <p>
+ Preview the given file in a browser context
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: File name within the project, defaults to session scope</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:openfile</h2>
+ <p>
+ Observe a request for a file to be opened and start the cycle:
+ <ul>
+ <li>Send event that you are opening up something (openbefore)</li>
+ <li>Ask the file system to load a file (loadFile)</li>
+ <li>If the file is loaded send an opensuccess event</li>
+ <li>If the file fails to load, send an openfail event</li>
+ </ul>
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: File name within the project, defaults to session scope</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:openfile:opensuccess</h2>
+ <p>
+ A sub-event of openfile, fired when the open was successful.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>file</code>: File object that was opened</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:openfile:openfail</h2>
+ <p>
+ A sub-event of openfile, fired when the open was a failure.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: Filename that wasn't able to open</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:openfile:openbefore</h2>
+ <p>
+ A sub-event of openfile, fired when the file is just about to be opened, giving you time to do something.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: Filename that wasn't able to open</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:forceopenfile</h2>
+ <p>
+ Open an existing file, or create a new one.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: File name within the project, defaults to session scope</li>
+ <li><code>content</code>: What to put into a new file</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:newfile</h2>
+ <p>
+ Create a new file
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>newfilename</code>: File name to create</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:savefile</h2>
+ <p>
+ Save a file
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: File name to save, defaults to session scope</li>
+ </ul>
+ </p>
+
+ <h2>bespin:editor:closefile</h2>
+ <p>
+ Close the given file (wrt the session)
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Name of the project to load from, defaults to session scope</li>
+ <li><code>filename</code>: File name within the project, defaults to session scope</li>
+ </ul>
+ </p>
+
+ <h2>bespin:url:change</h2>
+ <p>
+ Cause the #hashInfo to change.
+ </p>
+
+ <h2>bespin:url:changed</h2>
+ <p>
+ The #hashInfo has changed, so let people know.
+ </p>
+
+ <h2>bespin:session:status</h2>
+ <p>
+ Request the session status.
+ </p>
+
+
+ <h1 style="border-bottom: 1px solid #ccc">Settings Events</h1>
+
+ <p><code>bespin.client.settings.Events</code> houses a bunch of settings events.</p>
+
+ <h2>bespin:settings:set</h2>
+ <p>
+ Set a new setting. When run, a special <code>bespin:settings:set:SETTING_NAME</code> will be fired that you can catch.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>key</code>: setting</li>
+ <li><code>value</code>: to place for that setting</li>
+ </ul>
+ </p>
+
+ <h2>bespin:settings:syntax</h2>
+ <p>
+ Given a new syntax command, change the editor.language.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>language</code>: the computer language to change too</li>
+ <li><code>fromCommand</code>: Optionally, note if this event came in from a command</li>
+ </ul>
+ </p>
+
+ <h2>bespin:settings:init</h2>
+ <p>
+ The settings system has loaded, so do pre-initialization.
+ </p>
+ <p>
+ With arguments:
+ <ul>
+ <li><code>project</code>: Editor project</li>
+ <li><code>path</code>: Editor path to filename</li>
+ </ul>
+ </p>
+
+</div>
+</div>
+</body>
+</html>
View
124 docs/index.html
@@ -8,65 +8,85 @@
<body>
<div id="content">
<div id="overview" class="documentation" style="display: none;">
- <h1>Bespin</h1>
- <p><b>Overview</b></p>
- We are documenting the Bespin code as <a href="http://www.toolness.com/wp/?p=441">beautifully</a> as we can. For the front end JavaScript and Web code we are using the <a href="http://code.google.com/p/code-illuminated/">code illuminated tool</a> created by Atul Varma.</p>
- <p>Here is what we've got for documentation so far.</p>
- <h3>Bespin Front End Code</h3>
- <b>Core Files</b>
- <ul>
- <li><a href="#code/bespin.js">Bespin Core</a></li>
- <li><a href="#code/bootstrap.js">Editor Bootstrap Code</a></li>
- <li><a href="#code/events.js">Core Editor Events and <code>Bespin.Events</code> Helper</a></li>
- </ul>
+ <h1>Bespin</h1>
+ <p><b>Overview</b></p>
- <b>Client Code</b>
- <ul>
- <li><a href="#code/client/filesystem.js">FileSystem</a>: Remote File System</li>
- <li><a href="#code/client/server.js">Server</a>: Implementation of the Bespin Server API</li>
- <li><a href="#code/client/session.js">Session Module</a>: EditSession and SyncHelper</li>
- <li><a href="#code/client/settings.js">Settings Module</a>: Settings core, and various stores</li>
- </ul>
+ <p>We are documenting the Bespin code as <a href="http://www.toolness.com/wp/?p=441">beautifully</a> as we can. For the front end JavaScript and Web code we are using the <a href="http://code.google.com/p/code-illuminated/">code illuminated tool</a> created by Atul Varma.</p>
- <b>Command Line</b>
- <ul>
- <li><a href="#code/commandline/commandline.js">CommandLine: The core command line module</a></li>
- <li><a href="#code/commandline/commands.js">Commands</a>: The store that holds reused commands</li>
- <li><a href="#code/commandline/dashboardcommands.js">Dashboard Commands</a></li>
- <li><a href="#code/commandline/editorcommands.js">Editor Commands</a></li>
- </ul>
+ <p>Here is what we've got for documentation so far. We have split out the <a href="events.html">documentation for the Events API</a> which is something you will want to check out if you want a loosely coupled integration with Bespin.</p>
- <b>Editor</b>
- <ul>
- <li><a href="#code/editor/actions.js">Editor Actions</a>: what the editor can do</li>
- <li><a href="#code/editor/editor.js">Editor</a>: The meat. The Core. paint()</li>
- <li><a href="#code/editor/model.js">Model</a>: the document model the editor uses</li>
- <li><a href="#code/editor/syntaxhighlighter.js">Syntax Highlighter</a>: shim over to whatever highlighter is used</li>
- <li><a href="#code/editor/themes.js">Themes</a>: Current theming</li>
- <li><a href="#code/editor/toolbar.js">Toolbar</a>: link external toolbar to the editor itself</li>
- <li><a href="#code/editor/undo.js">Undo</a>: Manage the undo / redo queue</li>
- </ul>
+ <h3>Bespin Front End Code</h3>
+
+ <b>Core Files</b>
+ <ul>
+ <li><a href="#code/bespin/bespin.js">Bespin Core</a></li>
+ <li><a href="#code/bespin/bootstrap.js">Editor Bootstrap Code</a></li>
+ <li><a href="#code/bespin/events.js">Core Editor Events and <code>Bespin.Events</code> Helper</a></li>
+ </ul>
- <b>Syntax Highlighting</b>
- <ul>
- <li><a href="#code/syntax/syntax.js">Syntax Highlighter</a>: The core driver (SyntaxModel) and engine resolver (EngineResolver)</li>
- <li><a href="#code/syntax/javascript.js">JavaScript Syntax Engine</a></li>
- <li><a href="#code/syntax/css.js">CSS Syntax Engine</a></li>
- <li><a href="#code/syntax/html.js">HTML Syntax Engine</a></li>
+ <b>Client Code</b>
+ <ul>
+ <li><a href="#code/bespin/client/filesystem.js">FileSystem</a>: Remote File System</li>
+ <li><a href="#code/bespin/client/server.js">Server</a>: Implementation of the Bespin Server API</li>
+ <li><a href="#code/bespin/client/session.js">Session Module</a>: EditSession and SyncHelper</li>
+ <li><a href="#code/bespin/client/settings.js">Settings Module</a>: Settings core, and various stores</li>
+ </ul>
+
+ <b>Command Line</b>
+ <ul>
+ <li><a href="#code/bespin/cmd/commandline.js">CommandLine: The core command line module</a></li>
+ <li><a href="#code/bespin/cmd/commands.js">Commands</a>: The store that holds reused commands</li>
+ <li><a href="#code/bespin/cmd/dashboardcommands.js">Dashboard Commands</a></li>
+ <li><a href="#code/bespin/cmd/editorcommands.js">Editor Commands</a></li>
</ul>
+
+ <b>Dashboard</b>
+ <ul>
+ <li><a href="#code/bespin/dashboard/components.js">Dashboard Components</a>: Thunderhead components that the dashboard uses</li>
+ <li><a href="#code/bespin/dashboard/dashboard.js">Dashboard Core</a>: bootstrap of the dashboard page</li>
+ <li><a href="#code/bespin/dashboard/events.js">Dashboard Events</a>: definition of events that the dashboard publishes and subscribes to</li>
+ </ul>
+
+ <b>Editor</b>
+ <ul>
+ <li><a href="#code/bespin/editor/actions.js">Editor Actions</a>: what the editor can do</li>
+ <li><a href="#code/bespin/editor/editor.js">Editor</a>: The meat. The Core. paint()</li>
+ <li><a href="#code/bespin/editor/events.js">Events</a>: definition of events that the editor publishes and subscribes to</li>
+ <li><a href="#code/bespin/editor/model.js">Model</a>: the document model the editor uses</li>
+ <li><a href="#code/bespin/editor/themes.js">Themes</a>: Current theming</li>
+ <li><a href="#code/bespin/editor/toolbar.js">Toolbar</a>: link external toolbar to the editor itself</li>
+ <li><a href="#code/bespin/editor/undo.js">Undo</a>: Manage the undo / redo queue</li>
+ </ul>
+
+ <b>Syntax Highlighting</b>
+ <ul>
+ <li><a href="#code/bespin/syntax/syntax.js">Syntax Highlighter</a>: The core driver (SyntaxModel) and engine resolver (EngineResolver)</li>
+ <li><a href="#code/bespin/syntax/javascript.js">JavaScript Syntax Engine</a> (also registered for Java. Sneaky huh.)</li>
+ <li><a href="#code/bespin/syntax/css.js">CSS Syntax Engine</a></li>
+ <li><a href="#code/bespin/syntax/html.js">HTML Syntax Engine</a></li>
+ <li><a href="#code/bespin/syntax/php.js">PHP Syntax Engine</a></li>
+ <li><a href="#code/bespin/syntax/arduino.js">Arduino Syntax Engine</a></li>
+ </ul>
+
+ <b>User</b>
+ <ul>
+ <li><a href="#code/bespin/user/register.js">Register</a>: Code for user registration on a bespin server</li>
+ <li><a href="#code/bespin/user/utils.js">Utils</a>: Handy utilities for dealing with the front page of a bespin server</li>
+ </ul>
- <b>Utilities</b>
- <ul>
- <li><a href="#code/util/clipboard.js">Clipboard</a>: Handle the clipboard to copy and paste</li>
- <li><a href="#code/util/fixcanvas.js">Canvas</a>: shim it up, have the standard work all over</li>
- <li><a href="#code/util/keys.js">Key</a>: Key codes and simple handling</li>
- <li><a href="#code/util/navigate.js">Navigate</a>: Shoot people around the Bespin URLs (a.k.a. location.href wrapper)</li>
- <li><a href="#code/util/path.js">Path</a>: combine paths, get directories, and escape away</li>
- <li><a href="#code/util/textselection.js">TextSelection</a>: The ability to call $(elements).enableTextSelection to disable it</li>
- <li><a href="#code/util/tokenobject.js">TokenObject</a>: split up a string into pieces</li>
- <li><a href="#code/util/urlbar.js">URLBar</a> will watch the hash location and fire an event when it changes</li>
- </ul>
+ <b>Utilities</b>
+ <ul>
+ <li><a href="#code/bespin/util/canvas.js">Canvas</a>: shim it up, have the standard work all over</li>
+ <li><a href="#code/bespin/util/clipboard.js">Clipboard</a>: Handle the clipboard to copy and paste</li>
+ <li><a href="#code/bespin/util/keys.js">Key</a>: Key codes and simple handling</li>
+ <li><a href="#code/bespin/util/mousewheelevent.js">Mouse Wheel</a>: Wrap the mousewheel events to work cross browser, both vertical and horizontal</li>
+ <li><a href="#code/bespin/util/navigate.js">Navigate</a>: Shoot people around the Bespin URLs (a.k.a. location.href wrapper)</li>
+ <li><a href="#code/bespin/util/path.js">Path</a>: combine paths, get directories, and escape away</li>
+ <li><a href="#code/bespin/util/tokenobject.js">TokenObject</a>: split up a string into pieces</li>
+ <li><a href="#code/bespin/util/urlbar.js">URLBar</a>: will watch the hash location and fire an event when it changes</li>
+ <li><a href="#code/bespin/util/util.js">Util</a>: is the REAL grab bag. A lot of "what Prototype used to do that we liked", but more.</li>
+ </ul>
</div>
</div>
View
2  frontend/dashboard.html
@@ -31,7 +31,7 @@
<!-- begin script tags -->
<script type="text/javascript">
- var djConfig = { isDebug: false };
+ var djConfig = { isDebug: false, debugAtAllCosts: false };
</script>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
View
4 frontend/editor.html
@@ -31,7 +31,7 @@
<!-- begin script tags -->
<script type="text/javascript">
- var djConfig = { isDebug: false };
+ var djConfig = { isDebug: false, debugAtAllCosts: false };
</script>
<script type="text/javascript" src="js/dojo/dojo.js"></script>
@@ -47,7 +47,7 @@
<div id="collab_line"></div>
<div id="version"></div>
<a href="index.html" title="Go Home"><img id="logo" src="images/logo.png" alt="Bespin" border="0"></a>
- <a href="dashboard.html" id="hrefDashboard" title="Visit the Dashboard"><img id="toolbar_dashboard" src="images/icn_dashboard_off.png" alt="Dashboard" border="0"></a>
+ <a href="javascript:bespin.util.navigate.dashboard()" title="Visit the Dashboard"><img id="toolbar_dashboard" src="images/icn_dashboard_off.png" alt="Dashboard" border="0"></a>
<!-- coming soon -->
<!--<img id="toolbar_files" src="images/icn_files_off.png" alt="Files" title="Show Files">-->
View
8 frontend/index.html
@@ -30,6 +30,10 @@
<link type="text/css" rel="stylesheet" href="css/index.css"/>
<!-- begin script tags -->
+ <script type="text/javascript">
+ var djConfig = { isDebug: false, debugAtAllCosts: false };
+ </script>
+
<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
@@ -46,7 +50,7 @@
<form onsubmit="bespin.user.login(); return false;">
<input id="loginbutton" type="image" src="images/splash_btn_login.gif" />
<div id="login-holder"><span id="login_label"><a href="#" onclick="bespin.user.register.showForm();">Sign up</a> or log in:</span>
- <label for="username">Username: </label><input id="username" type="text" /> <label id="psw-label" for="password">Password: </label><input id="password" type="password" /> <div id="status" style="display: none;"></div>
+ <label for="username">Username: </label><input id="username" type="text" /> <label id="psw-label" for="password">Password: </label><input id="password" type="password" maxlength="20"/> <div id="status" style="display: none;"></div>
</div>
</form>
</div>
@@ -92,7 +96,7 @@
</tr>
<tr>
<th>Password:</th>
- <td><input type="password" id="register_password" onchange="bespin.user.register.checkPassword();"/></td>
+ <td><input type="password" id="register_password" maxlength="20" onchange="bespin.user.register.checkPassword();"/></td>
</tr>
<tr>
<td class="register_error" colspan="2">
View
2  frontend/js/bespin/bespin.js
@@ -39,7 +39,7 @@ dojo.provide("bespin.bespin");
dojo.mixin(bespin, {
// BEGIN VERSION BLOCK
versionNumber: 'tip',
- versionCodename: '(none)',
+ versionCodename: 'DEVELOPMENT MODE',
apiVersion: 'dev',
// END VERSION BLOCK
View
10 frontend/js/bespin/bootstrap.js
@@ -68,13 +68,7 @@ dojo.addOnLoad(function(){
_toolbar.setupDefault();
_editor.setFocus(true);
-
- // Adds information for dashboard where to jump back again
- var urlParameter = dojo.queryToObject(location.hash.substring(1));
- if (urlParameter.pathSelected) {
- dojo.byId('hrefDashboard').href = "dashboard.html#pathSelected=" + urlParameter.pathSelected;
- }
-
+
// Force a login just in case the user session isn't around
_server.currentuser(isLoggedIn, isNotLoggedIn);
@@ -130,7 +124,7 @@ dojo.addOnLoad(function(){
//
// Save the users magic project into the session
function isLoggedIn(userinfo) {
- _editSession.username = userinfo.username;
+ _editSession.setUserinfo(userinfo);
_settings = new bespin.client.settings.Core();
_commandLine = new bespin.cmd.commandline.Interface(dojo.byId('command'), bespin.cmd.editorcommands.Commands);
View
2  frontend/js/bespin/client/filesystem.js
@@ -127,7 +127,7 @@ dojo.declare("bespin.client.FileSystem", null, {
saveFile: function(project, file) {
// Unix files should always have a trailing new-line; add if not present
if (/\n$/.test(file.content)) file.content += "\n";
-
+
_server.saveFile(project, file.name, file.content, file.lastOp);
},
View
1  frontend/js/bespin/client/server.js
@@ -381,6 +381,7 @@ dojo.declare("bespin.client.Server", null, {
// * {{{archivetype}}} is either zip | tgz
exportProject: function(project, archivetype) {
if (bespin.util.include(['zip','tgz','tar.gz'], archivetype)) {
+// console.log('/project/export', project + "." + archivetype);
var iframe = document.createElement("iframe");
iframe.src = bespin.util.path.combine('/project/export', project + "." + archivetype);
iframe.style.display = 'none';
View
11 frontend/js/bespin/client/session.js
@@ -47,8 +47,13 @@ dojo.declare("bespin.client.session.EditSession", null, {
},
projectForDisplay: function(testProject) {
- var project = testProject || this.project;
- return project;
+ return testProject || this.project;
+ },
+
+ setUserinfo: function(userinfo) {
+ this.username = userinfo.username;
+ this.amountUsed = userinfo.amountUsed;
+ this.quota = userinfo.quota;
},
checkSameFile: function(project, path) {
@@ -102,7 +107,7 @@ dojo.declare("bespin.client.session.SyncHelper", null, {
var ops = eval(json);
this.lastOp += ops.length;
- ops.each(function(op) {
+ dojo.forEach(ops, function(op) {
if (op.username != _editSession.username) { // don't play operations that have been performed by this user
self.playOp(op);
_showCollabHotCounter = 20;
View
2  frontend/js/bespin/client/settings.js
@@ -221,7 +221,7 @@ dojo.declare("bespin.client.settings.Server", null, {
// TODO: seed the settings
this.server.listSettings(dojo.hitch(this, function(settings) {
this.settings = settings;
- if (settings['tabsize'] == undefined) {
+ if (settings['tabsize'] === undefined) {
this.settings = this.parent.defaultSettings();
this.server.setSettings(this.settings);
}
View
21 frontend/js/bespin/cmd/commandline.js
@@ -47,6 +47,7 @@ dojo.declare("bespin.cmd.commandline.Interface", null, {
if (window['_editor']) this.editor = _editor;
this.inCommandLine = false;
+ this.suppressInfo = false; // When true, info bar popups will not be shown
this.commands = {};
this.aliases = {};
@@ -134,8 +135,10 @@ dojo.declare("bespin.cmd.commandline.Interface", null, {
var usage = command.usage || "no usage information found for " + command.name;
this.showInfo("Usage: " + command.name + " " + usage, autohide);
},
-
+
showInfo: function(html, autohide) {
+ if (this.suppressInfo) return; // bypass
+
this.hideInfo();
dojo.byId('info').innerHTML = html;
@@ -444,6 +447,20 @@ dojo.declare("bespin.cmd.commandline.Events", null, {
if (msg) commandline.showInfo(msg, autohide);
});
+ // ** {{{ Event: bespin:cmdline:suppressinfo }}} **
+ //
+ // Turn on info bar suppression
+ bespin.subscribe("bespin:cmdline:suppressinfo", function(event) {
+ commandline.suppressInfo = true;
+ });
+
+ // ** {{{ Event: bespin:cmdline:unsuppressinfo }}} **
+ //
+ // Turn off info bar suppression
+ bespin.subscribe("bespin:cmdline:unsuppressinfo", function(event) {
+ commandline.suppressInfo = false;
+ });
+
// ** {{{ Event: bespin:cmdline:executed }}} **
//
// Once the command has been executed, do something.
@@ -496,7 +513,7 @@ dojo.declare("bespin.cmd.commandline.Events", null, {
var project = event.project;
_editSession.project = project;
- commandline.showInfo('Changed project to ' + project, true);
+ if (!event.suppressPopup) commandline.showInfo('Changed project to ' + project, true);
});
}
View
18 frontend/js/bespin/cmd/commands.js
@@ -124,7 +124,7 @@ bespin.cmd.commands.add({
}
} else {
var key = setting.key;
- if (setting.value == undefined) { // show it
+ if (setting.value === undefined) { // show it
var value = self.settings.get(key);
if (value) {
output = "<u>Your setting</u><br/><br/>";
@@ -603,6 +603,18 @@ bespin.cmd.commands.add({
}
});
+// ** {{{Command: quota}}} **
+bespin.cmd.commands.add({
+ name: 'quota',
+ preview: 'show your quota info',
+ megabytes: function(bytes) {
+ return (bytes / 1024 / 1024).toFixed(2);
+ },
+ execute: function(self) {
+ self.showInfo("You have used " + this.megabytes(_editSession.amountUsed) + " MB out of your " + this.megabytes(_editSession.quota) + " MB quota");
+ }
+});
+
// ** {{{Command: export}}} **
bespin.cmd.commands.add({
name: 'export',
@@ -613,7 +625,7 @@ bespin.cmd.commands.add({
var project = args.project || _editSession.project;
var type = args.archivetype;
- if (!bespin.util.include(['zip','tgz','tar.gz'], archivetype)) {
+ if (!bespin.util.include(['zip','tgz','tar.gz'], type)) {
type = 'zip';
}
@@ -798,7 +810,7 @@ bespin.cmd.commands.add({
output += x + ": " + self.aliases[x] + "<br/>";
}
} else {
- if (args.command == undefined) { // show it
+ if (args.command === undefined) { // show it
output = "<u>Your alias</u><br/><br/>";
var alias = self.aliases[args.alias];
if (alias) {
View
4 frontend/js/bespin/dashboard/components.js
@@ -223,7 +223,7 @@ dojo.declare("bespin.dashboard.components.BespinProjectPanel", th.components.Pan
this.oldPaint(ctx);
};
- this.list = new th.components.List({ style: { backgroundColor: "rgb(61, 59, 52)", color: "white", font: "8pt Tahoma" } });
+ this.list = new th.components.List({ allowDeselection: false, style: { backgroundColor: "rgb(61, 59, 52)", color: "white", font: "8pt Tahoma" } });
this.splitter = new th.components.Splitter({ orientation: th.HORIZONTAL });
@@ -275,7 +275,5 @@ dojo.declare("bespin.dashboard.components.BespinProjectPanel", th.components.Pan
this.footer.bounds = { x: d.i.l, y: d.b.h - ph, width: innerWidth, height: ph };
this.list.bounds = { x: d.i.l, y: y, width: innerWidth, height: this.splitter.bounds.height };
-
-
}
});
View
182 frontend/js/bespin/dashboard/dashboard.js
@@ -43,7 +43,7 @@ dojo.provide("bespin.dashboard.dashboard");
dojo.mixin(bespin.dashboard, {
projects: null,
tree: null,
- _fetchFilesAndReplace: null, // is needed, as this function is calling herselfe again... (better solution?)
+ lastSelectedPath: null,
sizeCanvas: function(canvas) {
if (!heightDiff) {
@@ -54,7 +54,8 @@ dojo.provide("bespin.dashboard.dashboard");
},
loggedIn: function(userinfo) {
- _editSession.username = userinfo.username;
+ _editSession.setUserinfo(userinfo);
+
_server.list(null, null, bd.displayProjects); // get projects
_server.listOpen(bd.displaySessions); // get sessions
},
@@ -117,21 +118,6 @@ dojo.provide("bespin.dashboard.dashboard");
tree.render();
});
},
-
- fetchFilesAndRelace: function(wholePath, index, tree) {
- var path = wholePath.slice(0, index);
- var filepath = currentProject + "/" + bd.getFilePath(path);
-
- _server.list(filepath, null, function(files) {
- tree.replaceList(path.length, bd.prepareFilesForTree(files));
- if (index != 0) {
- bd.tree.lists[index].selectItemByText(wholePath[index].name);
- bespin.dashboard._fetchFilesAndReplace(wholePath, index - 1, tree);
- } else {
- bd.tree.lists[0].selectItemByText(wholePath[0].name);
- }
- });
- },
displaySessions: function(sessions) {
infoPanel.removeAll();
@@ -158,6 +144,103 @@ dojo.provide("bespin.dashboard.dashboard");
// }, 3000);
},
+ restorePath: function(newPath) {
+ bd.lastSelectedPath = bd.lastSelectedPath || '';
+ newPath = newPath || '';
+ var oldPath = bd.lastSelectedPath;
+ bd.lastSelectedPath = newPath;
+
+ if (newPath == oldPath && newPath != '') return; // the path has not changed
+
+ newPath = newPath.split('/');
+ oldPath = oldPath.split('/');
+ currentProject = newPath[0];
+
+ scene.renderAllowed = false;
+
+ projects.list.selectItemByText(newPath[0]); // this also perform a rendering of the project.list
+
+ if (newPath[0] == '') {
+ // this is true if no path is given via URL
+ bd.tree.removeListsFrom(0);
+ bd.projects.list.selected = null;
+ scene.renderAllowed = true;
+ scene.render();
+ return;
+ }
+
+ var sameLevel = 1;
+ while (sameLevel < Math.min(newPath.length, oldPath.length) && newPath[sameLevel] == oldPath[sameLevel]) {
+ sameLevel ++;
+ }
+
+ var fakePath = new Array(newPath.length);
+ for (var x = 1; x < newPath.length; x++) {
+ var fakeItem = new Object();
+ fakeItem.name = newPath[x];
+ if (x != newPath.length - 1) {
+ fakeItem.contents = 'fake';
+ }
+ if (x > bd.tree.lists.length - 1) {
+ bd.tree.showChildren(null, new Array(fakeItem));
+ }
+ if (newPath[x] != '') {
+ bd.tree.lists[x - 1].selectItemByText(newPath[x]);
+ }
+ fakePath[x] = fakeItem;
+ }
+
+ if (newPath.length <= bd.tree.lists.length) {
+ bd.tree.removeListsFrom(newPath.length - 1);
+ }
+
+ var contentsPath = new Array(newPath.length);
+ var countSetupPaths = sameLevel;
+
+ // this function should stay here, as this funciton is accessing "pathContents" and "countSetupPaths"
+ var displayFetchedFiles = function(files) {
+ // "this" is the callbackData object!
+ var contents = bd.prepareFilesForTree(files);
+ if (this.index != 0) {
+ contentsPath[this.listIndex] = contents;
+ }
+
+ bd.tree.replaceList(this.index, contents);
+ bd.tree.lists[this.index].selectItemByText(fakePath[this.index + 1].name);
+ countSetupPaths ++;
+
+ if (countSetupPaths == newPath.length) {
+ for (var x = 0; x < newPath.length - 1; x++) {
+ // when the path is not restored from the root, then there are contents without contents!
+ if (contentsPath[x + 1]) {
+ bd.tree.lists[x].selected.contents = contentsPath[x + 1];
+ }
+ }
+ }
+ }
+
+ for (var x = sameLevel; x < newPath.length; x++) {
+ var selected = (x != 0 ? bd.tree.lists[x - 1].selected : null);
+ if (selected && selected.contents && dojo.isArray(selected.contents)) {
+ // restore filelist from local memory (the filelists was ones fetched)
+ if (x > bd.tree.lists.length - 1) {
+ bd.tree.showChildren(null, selected.contents)
+ } else {
+ bd.tree.replaceList(x, selected.contents);
+ }
+ bd.tree.lists[x].selectItemByText(fakePath[x].name);
+ countSetupPaths ++;
+ } else {
+ // load filelist form server
+ var filepath = currentProject + "/" + bd.getFilePath(fakePath.slice(1, x));
+ _server.list(filepath, null, dojo.hitch({index: x - 1}, displayFetchedFiles));
+ }
+ }
+
+ scene.renderAllowed = true;
+ scene.render();
+ },
+
displayProjects: function(projectItems) {
for (var i = 0; i < projectItems.length; i++) {
projectItems[i] = projectItems[i].name.substring(0, projectItems[i].name.length - 1);
@@ -165,57 +248,11 @@ dojo.provide("bespin.dashboard.dashboard");
projects.list.items = projectItems;
// Restore the last selected file
- var urlParameter = dojo.queryToObject(location.hash.substring(1));
- var pathSelected = urlParameter['pathSelected'] || false;
+ var path = (new bespin.client.settings.URL()).get('path');
- if (pathSelected) {
- pathSelected = pathSelected.split('/');
- var projectSelected = pathSelected.shift();
- projects.list.selectItemByText(projectSelected); // this also perform a rendering of the project.list
- currentProject = projectSelected;
-
- _server.list(projectSelected, null, function(files) {
- // suppress the scene to be rendered as there is a lot of suff going on that would each time call
- // a scene.repaint() / render()
- scene.suppressPaintAndRender = true;
-
- bd.displayFiles(files);
- bd.tree.lists[0].selectItemByText(pathSelected[0]);
-
- if (pathSelected.length <= 1) {
- scene.suppressPaintAndRender = false;
- scene.render();
- return;
- }
-
- // creates new lists, but only with one entry (the one needed to get to the end of the path)
- var fakePath = [];
- for (var x = 1; x < pathSelected.length-1; x++) {
- bd.tree.showChildren(null, new Array({name: pathSelected[x], contents: 'noRealContents'}));
- bd.tree.lists[x].selectItemByText(pathSelected[x]);
- fakePath.push({name: pathSelected[x - 1]});
- }
-
- // guess the last item of the path is not a directory => no contents for this item
- bd.tree.showChildren(null, new Array({name: pathSelected[pathSelected.length-1]}));
- // select the last list item only if the selectPath doesn't end on an folder (bespin/commands/ = >['besin','commands',''])
- if (pathSelected[pathSelected.length-1] != '') {
- bd.tree.lists[pathSelected.length-1].selectItemByText(pathSelected[pathSelected.length-1]);
- }
-
- fakePath.push({name: pathSelected[pathSelected.length-2]});
- fakePath.push({name: pathSelected[pathSelected.length - 1]});
-
- // turn rendering on again and render the fakepath
- scene.suppressPaintAndRender = false;
- scene.render();
-
- // load now the lists corretly (displaying all the files in the directory etc.)
- bespin.dashboard._fetchFilesAndReplace = bd.fetchFilesAndRelace;
- bd.fetchFilesAndRelace(fakePath, fakePath.length-1, bd.tree);
- });
+ if (!bd.lastSelectedPath) {
+ bd.restorePath(path);
} else {
- scene.suppressPaintAndRender = false;
scene.render();
}
},
@@ -323,10 +360,11 @@ dojo.provide("bespin.dashboard.dashboard");
});
scene.bus.bind("itemselected", projects.list, function(e) {
- currentProject = e.item;
+ bespin.dashboard.lastSelectedPath = e.item + '/';
+ location.hash = '#path=' + e.item + '/';
_server.list(e.item, null, bd.displayFiles);
- bespin.publish("bespin:project:set", { project: currentProject });
- bespin.publish("bespin:editor:project:set", { project: currentProject });
+ currentProject = e.item;
+ bespin.publish("bespin:project:set", { project: currentProject, suppressPopup: true });
});
// setup the command line
@@ -344,5 +382,11 @@ dojo.provide("bespin.dashboard.dashboard");
// get logged in name; if not logged in, display an error of some kind
_server.currentuser(bd.loggedIn, bd.notLoggedIn);
+
+ // provide history for the dashboard
+ bespin.subscribe("bespin:url:changed", function(e) {
+ var pathSelected = (new bespin.client.settings.URL()).get('path');
+ bespin.dashboard.restorePath(pathSelected);
+ });
});
})();
View
1  frontend/js/bespin/dashboard/dependencies.js
@@ -35,6 +35,7 @@ dojo.require("bespin.util.navigate");
dojo.require("bespin.util.path");
dojo.require("bespin.util.tokenobject");
dojo.require("bespin.util.clipboard");
+dojo.require("bespin.util.urlbar");
dojo.require("bespin.client.filesystem");
dojo.require("bespin.client.settings");
View
93 frontend/js/bespin/editor/actions.js
@@ -60,7 +60,7 @@ dojo.declare("bespin.editor.Actions", null, {
if (originalRow > 0) this.moveToLineEnd(args);
return;
}
- this.editor.cursorPosition.col = Math.max(0, args.pos.col - 1);
+ this.editor.moveCursor({ col: Math.max(0, args.pos.col - 1) });
this.handleCursorSelection(args);
this.repaint();
},
@@ -74,13 +74,13 @@ dojo.declare("bespin.editor.Actions", null, {
return;
}
- this.editor.cursorPosition.col = args.pos.col + 1;
+ this.editor.moveCursor({ col: args.pos.col + 1 });
this.handleCursorSelection(args);
this.repaint();
},
moveCursorUp: function(args) {
- this.editor.cursorPosition.row = Math.max(0, args.pos.row - 1);
+ this.editor.moveCursor({ row: Math.max(0, args.pos.row - 1) });
if (_settings.isOn(_settings.get('strictlines')) && args.pos.col > this.editor.model.getRowLength(this.editor.cursorPosition.row)) {
this.handleCursorSelection(args);
@@ -98,7 +98,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveCursorDown: function(args) {
- this.editor.cursorPosition.row = Math.min(this.editor.model.getRowCount() - 1, args.pos.row + 1);
+ this.editor.moveCursor({ row: Math.min(this.editor.model.getRowCount() - 1, args.pos.row + 1) });
if (_settings.isOn(_settings.get('strictlines')) && args.pos.col > this.editor.model.getRowLength(this.editor.cursorPosition.row)) {
this.handleCursorSelection(args);
@@ -116,7 +116,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveToLineStart: function(args) {
- var line = this.editor.model.getRowArray(this.editor.cursorPosition.row).join('');
+ var line = this.editor.model.getRowString(this.editor.cursorPosition.row, this.editor.ui.tabstop);
var match = /^(\s+).*/.exec(line);
var leadingWhitespaceLength = 0;
@@ -126,11 +126,11 @@ dojo.declare("bespin.editor.Actions", null, {
}
if (args.pos.col == 0) {
- this.editor.cursorPosition.col = leadingWhitespaceLength;
+ this.editor.moveCursor({ col: leadingWhitespaceLength });
} else if (args.pos.col == leadingWhitespaceLength) {
- this.editor.cursorPosition.col = 0;
+ this.editor.moveCursor({ col: 0 });
} else {
- this.editor.cursorPosition.col = leadingWhitespaceLength;
+ this.editor.moveCursor({ col: leadingWhitespaceLength });
}
this.handleCursorSelection(args);
@@ -141,7 +141,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveToLineEnd: function(args) {
- this.editor.cursorPosition.col = this.editor.model.getRowLength(args.pos.row);
+ this.editor.moveCursor({ col: this.editor.model.getRowLength(args.pos.row) });
this.handleCursorSelection(args);
this.repaint();
@@ -150,7 +150,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveToFileTop: function(args) {
- this.editor.cursorPosition.col = this.editor.cursorPosition.row = 0;
+ this.editor.moveCursor({ col: this.editor.cursorPosition.row = 0 });
this.handleCursorSelection(args);
this.repaint();
@@ -161,8 +161,8 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveToFileBottom: function(args) {
- this.editor.cursorPosition.row = this.editor.model.getRowCount() - 1;
- this.editor.cursorPosition.col = this.editor.model.getRowLength(this.editor.cursorPosition.row);
+ var row = this.editor.model.getRowCount() - 1;
+ this.editor.moveCursor({ row: row, col: this.editor.model.getRowLength(row)});
this.handleCursorSelection(args);
this.repaint();
@@ -173,7 +173,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
movePageUp: function(args) {
- this.editor.cursorPosition.row = Math.max(this.editor.ui.firstVisibleRow - this.editor.ui.visibleRows, 0);
+ this.editor.moveCursor({ row: Math.max(this.editor.ui.firstVisibleRow - this.editor.ui.visibleRows, 0)});
this.handleCursorSelection(args);
this.repaint();
@@ -182,7 +182,7 @@ dojo.declare("bespin.editor.Actions", null, {
},
movePageDown: function(args) {
- this.editor.cursorPosition.row = Math.min(this.editor.cursorPosition.row + this.editor.ui.visibleRows, this.editor.model.getRowCount() - 1);
+ this.editor.moveCursor({ row: Math.min(this.editor.cursorPosition.row + this.editor.ui.visibleRows, this.editor.model.getRowCount() - 1)});
this.handleCursorSelection(args);
this.repaint();
@@ -191,7 +191,8 @@ dojo.declare("bespin.editor.Actions", null, {
},
moveWordLeft: function(args) {
- var row = this.editor.model.getRowArray(args.pos.row);
+ var row = this.editor.model.getRowString(args.pos.row);
+
var c, charCode;
if (args.pos.col == 0) { // -- at the start to move up and to the end
@@ -211,9 +212,9 @@ dojo.declare("bespin.editor.Actions", null, {
while (newcol > 0) {
newcol--;
- c = row[newcol];
+ c = row.charAt(newcol);
charCode = c.charCodeAt(0);
- if (charCode == 32) {
+ if (charCode == 32 /*space*/) {
wasSpaces = true;
} else {
newcol++;
@@ -225,7 +226,7 @@ dojo.declare("bespin.editor.Actions", null, {
if (!wasSpaces) {
while (newcol > 0) {
newcol--;
- c = row[newcol];
+ c = row.charAt(newcol);
charCode = c.charCodeAt(0);
if ( (charCode < 65) || (charCode > 122) ) { // if you get to an alpha you are done
if (newcol != args.pos.col - 1) newcol++; // right next to a stop char, move back one
@@ -234,13 +235,13 @@ dojo.declare("bespin.editor.Actions", null, {
}
}
- this.editor.cursorPosition.col = newcol;
+ this.editor.moveCursor({ col: newcol });
this.handleCursorSelection(args);
this.repaint();
},
moveWordRight: function(args) {
- var row = this.editor.model.getRowArray(args.pos.row);
+ var row = this.editor.model.getRowString(args.pos.row);
var c, charCode;
if (row.length <= args.pos.col) { // -- at the edge so go to the next line
@@ -255,7 +256,7 @@ dojo.declare("bespin.editor.Actions", null, {
while (newcol < row.length) {
c = row[newcol];
charCode = c.charCodeAt(0);
- if (charCode == 32) {
+ if (charCode == 32 /*space*/) {
wasSpaces = true;
newcol++;
} else {
@@ -282,19 +283,11 @@ dojo.declare("bespin.editor.Actions", null, {
}
}
- this.editor.cursorPosition.col = newcol;
+ this.editor.moveCursor({ col: newcol });
this.handleCursorSelection(args);
this.repaint();
},
- undoRedo: function(args) {
- if (! args.event.shiftKey) { // holding down the shift key causes the undo keystroke to be a redo TODO: move this logic to key handler
- this.undo();
- } else {
- this.redo();
- }
- },
-
undo: function() {
this.editor.undoManager.undo();
},
@@ -328,14 +321,21 @@ dojo.declare("bespin.editor.Actions", null, {
return;
}
- var tabWidth = parseInt(_settings.get('tabsize') || bespin.defaultTabSize); // TODO: global needs fixing
- var tabWidthCount = tabWidth;
- var tab = "";
- while (tabWidthCount-- > 0) {
- tab += " ";
+ var realTabs = (_settings.get('tabmode') == 'tabs');
+ if (realTabs) {
+ // do something tabby
+ tab = "\t";
+ tabWidth = 1;
+ } else {
+ var tabWidth = parseInt(_settings.get('tabsize') || bespin.defaultTabSize); // TODO: global needs fixing
+ var tabWidthCount = tabWidth;
+ var tab = "";
+ while (tabWidthCount-- > 0) {
+ tab += " ";
+ }
}
-
- this.editor.model.insertCharacters({row: args.pos.row, col: args.pos.col}, tab);
+
+ this.editor.model.insertCharacters({row: args.pos.row, col: args.pos.col}, tab);
this.editor.moveCursor({row: args.pos.row, col: args.pos.col + tabWidth});
this.repaint();
@@ -402,7 +402,7 @@ dojo.declare("bespin.editor.Actions", null, {
this.editor.setSelection(selection);
}
args.pos.col += (historyIndent ? historyIndent[historyIndent.length-1] : tab.length);
- this.editor.cursorPosition.col = args.pos.col;
+ this.editor.moveCursor({ col: args.pos.col });
historyIndent = historyIndent ? historyIndent : newHistoryIndent;
this.repaint();
@@ -457,7 +457,7 @@ dojo.declare("bespin.editor.Actions", null, {
args.pos.col = Math.max(0, args.pos.col - charsToDelete);
}
}
- this.editor.cursorPosition.col = args.pos.col;
+ this.editor.moveCursor({ col: args.pos.col });
if (!fakeSelection) {
this.editor.setSelection(selection);
@@ -542,7 +542,7 @@ dojo.declare("bespin.editor.Actions", null, {
// NOTE: Actually, clipboard.js is taking care of this unless EditorOnly mode is set
pasteFromClipboard: function(args) {
var clipboard = (args.clipboard) ? args.clipboard : bespin.util.clipboard.Manual.data();
- if (clipboard == undefined) return; // darn it clipboard!
+ if (clipboard === undefined) return; // darn it clipboard!
args.chunk = clipboard;
this.insertChunk(args);
},
@@ -656,7 +656,7 @@ dojo.declare("bespin.editor.Actions", null, {
this.deleteSelection(args);
} else {
if (args.pos.col > 0) {
- this.editor.cursorPosition.col = Math.max(0, args.pos.col - 1);
+ this.editor.moveCursor({ col: Math.max(0, args.pos.col - 1) });
args.pos.col -= 1;
this.deleteCharacter(args);
} else {
@@ -696,8 +696,7 @@ dojo.declare("bespin.editor.Actions", null, {
newline: function(args) {
var autoindentAmount = _settings.get('autoindent') ? bespin.util.leadingSpaces(this.editor.model.getRowArray(args.pos.row)) : 0;
this.editor.model.splitRow(args.pos, autoindentAmount);
- this.editor.cursorPosition.row += 1;
- this.editor.cursorPosition.col = autoindentAmount;
+ this.editor.moveCursor({ row: this.editor.cursorPosition.row + 1, col: autoindentAmount });
// undo/redo
args.action = "newline";
@@ -757,7 +756,7 @@ dojo.declare("bespin.editor.Actions", null, {
this.deleteSelectionAndInsertCharacter(args);
} else {
this.editor.model.insertCharacters(args.pos, args.newchar);
- this.editor.cursorPosition.col += 1;
+ this.editor.moveCursor({ col: this.editor.cursorPosition.col + 1 });
this.repaint();
// undo/redo
@@ -773,12 +772,12 @@ dojo.declare("bespin.editor.Actions", null, {
var saveCursorRow = this.editor.cursorPosition.row;
var halfRows = Math.floor(this.editor.ui.visibleRows / 2);
if (saveCursorRow > (this.editor.ui.firstVisibleRow + halfRows)) { // bottom half, so move down
- this.editor.cursorPosition.row = this.editor.cursorPosition.row + halfRows;
+ this.editor.moveCursor({ row: this.editor.cursorPosition.row + halfRows });
} else { // top half, so move up
- this.editor.cursorPosition.row = this.editor.cursorPosition.row - halfRows;
+ this.editor.moveCursor({ row: this.editor.cursorPosition.row - halfRows });
}
this.editor.ui.ensureCursorVisible();
- this.editor.cursorPosition.row = saveCursorRow;
+ this.editor.moveCursor({ row: saveCursorRow });
},
repaint: function() {
View
63 frontend/js/bespin/editor/editor.js
@@ -254,8 +254,19 @@ dojo.declare("bespin.editor.DefaultEditorKeyListener", null, {
bindKeyString: function(modifiers, keyCode, action) {
var ctrlKey = (modifiers.toUpperCase().indexOf("CTRL") != -1);
var altKey = (modifiers.toUpperCase().indexOf("ALT") != -1);
- var metaKey = (modifiers.toUpperCase().indexOf("META") != -1) || (modifiers.toUpperCase().indexOf("APPLE") != -1) || (modifiers.toUpperCase().indexOf("CMD") != -1);
+ var metaKey = (modifiers.toUpperCase().indexOf("META") != -1) || (modifiers.toUpperCase().indexOf("APPLE") != -1);
var shiftKey = (modifiers.toUpperCase().indexOf("SHIFT") != -1);
+
+ // Check for the platform specific key type
+ // The magic "CMD" means metaKey for Mac (the APPLE or COMMAND key)
+ // and ctrlKey for Windows (CONTROL)
+ if (modifiers.toUpperCase().indexOf("CMD") != -1) {
+ if (bespin.util.isMac()) {
+ metaKey = true;
+ } else {
+ ctrlKey = true;
+ }
+ }
return this.bindKey(keyCode, metaKey, ctrlKey, altKey, shiftKey, action);
},
@@ -425,7 +436,7 @@ dojo.declare("bespin.editor.UI", null, {
convertClientPointToCursorPoint: function(pos) {
var x, y;
- if (y > (this.lineHeight * this.editor.model.getRowCount())) {
+ if (pos.y > (this.lineHeight * this.editor.model.getRowCount())) {
y = this.editor.model.getRowCount() - 1;
} else {
var ty = pos.y;
@@ -438,7 +449,7 @@ dojo.declare("bespin.editor.UI", null, {
var tx = pos.x - this.GUTTER_WIDTH - this.LINE_INSETS.left;
x = Math.floor(tx / this.charWidth);
- // With striclines turned on, don't select past the end of the line
+ // With strictlines turned on, don't select past the end of the line
if (_settings.isOn(_settings.get('strictlines'))) {
var maxcol = this.editor.model.getRowLength(y);
@@ -447,7 +458,6 @@ dojo.declare("bespin.editor.UI", null, {
}
}
}
-
return { col: x, row: y };
},
@@ -651,9 +661,9 @@ dojo.declare("bespin.editor.UI", null, {
listener.bindKeyStringSelectable("ALT", Key.ARROW_RIGHT, this.actions.moveWordRight);
listener.bindKeyStringSelectable("", Key.HOME, this.actions.moveToLineStart);
- listener.bindKeyStringSelectable("APPLE", Key.ARROW_LEFT, this.actions.moveToLineStart);
+ listener.bindKeyStringSelectable("CMD", Key.ARROW_LEFT, this.actions.moveToLineStart);
listener.bindKeyStringSelectable("", Key.END, this.actions.moveToLineEnd);
- listener.bindKeyStringSelectable("APPLE", Key.ARROW_RIGHT, this.actions.moveToLineEnd);
+ listener.bindKeyStringSelectable("CMD", Key.ARROW_RIGHT, this.actions.moveToLineEnd);
listener.bindKeyString("CTRL", Key.K, this.actions.killLine);
listener.bindKeyString("CTRL", Key.L, this.actions.moveCursorRowToCenter);
@@ -664,14 +674,13 @@ dojo.declare("bespin.editor.UI", null, {
listener.bindKeyString("", Key.TAB, this.actions.insertTab);
listener.bindKeyString("SHIFT", Key.TAB, this.actions.unindent);
- listener.bindKeyString("APPLE", Key.A, this.actions.selectAll);
- listener.bindKeyString("CTRL", Key.A, this.actions.selectAll);
+ listener.bindKeyString("CMD", Key.A, this.actions.selectAll);
- listener.bindKeyString("APPLE", Key.Z, this.actions.undoRedo);
- listener.bindKeyString("CTRL", Key.Z, this.actions.undoRedo);
+ listener.bindKeyString("CMD", Key.Z, this.actions.undo);
+ listener.bindKeyString("SHIFT CMD", Key.Z, this.actions.redo);
- listener.bindKeyStringSelectable("APPLE", Key.ARROW_UP, this.actions.moveToFileTop);
- listener.bindKeyStringSelectable("APPLE", Key.ARROW_DOWN, this.actions.moveToFileBottom);
+ listener.bindKeyStringSelectable("CMD", Key.ARROW_UP, this.actions.moveToFileTop);
+ listener.bindKeyStringSelectable("CMD", Key.ARROW_DOWN, this.actions.moveToFileBottom);
listener.bindKeyStringSelectable("", Key.PAGE_UP, this.actions.movePageUp);
listener.bindKeyStringSelectable("", Key.PAGE_DOWN, this.actions.movePageDown);
@@ -925,6 +934,8 @@ dojo.declare("bespin.editor.UI", null, {
ctx.fillRect(tx, y, tw, this.lineHeight);
}
+ var lineText = this.editor.model.getRowString(currentLine);
+
// the following two chunks of code do the same thing; only one should be uncommented at a time
// CHUNK 1: this code just renders the line with white text and is for testing
@@ -932,7 +943,8 @@ dojo.declare("bespin.editor.UI", null, {
// ctx.fillText(this.editor.model.getRowArray(currentLine).join(""), x, cy);
// CHUNK 2: this code uses new the SyntaxModel API to attempt to render a line with fewer passes than the color helper API
- var lineInfo = this.syntaxModel.getSyntaxStyles(currentLine, this.editor.language);
+
+ var lineInfo = this.syntaxModel.getSyntaxStyles(lineText, currentLine, this.editor.language);
for (ri = 0; ri < lineInfo.regions.length; ri++) {
var styleInfo = lineInfo.regions[ri];
@@ -1278,8 +1290,27 @@ dojo.declare("bespin.editor.API", null, {
},
moveCursor: function(newpos) {
+ if (!newpos) return; // guard against a bad position (certain redo did this)
+ if (newpos.col === undefined) newpos.col = this.cursorPosition.col;
+ if (newpos.row === undefined) newpos.row = this.cursorPosition.row;
+
+ var oldpos = this.cursorPosition;
+
var row = Math.min(newpos.row, this.model.getRowCount() - 1); // last row if you go over
if (row < 0) row = 0; // can't move negative off screen
+
+ var invalid = this.model.isInvalidCursorPosition(row, newpos.col);
+ if (invalid) {
+ if (oldpos.col < newpos.col) {
+ newpos.col = invalid.right;
+ } else if (oldpos.col > newpos.col) {
+ newpos.col = invalid.left;
+ } else {
+ // default
+ newpos.col = invalid.left;
+ }
+ }
+
this.cursorPosition = { row: row, col: newpos.col };
},
@@ -1372,7 +1403,11 @@ dojo.declare("bespin.editor.Events", null, {
var action = editor.ui.actions[event.action] || event.action;
if (keyCode && action) {
- editor.editorKeyListener.bindKeyString(modifiers, keyCode, action);
+ if (event.selectable) { // register the selectable binding to (e.g. SHIFT + what you passed in)
+ editor.editorKeyListener.bindKeyStringSelectable(modifiers, keyCode, action);
+ } else {
+ editor.editorKeyListener.bindKeyString(modifiers, keyCode, action);
+ }
}
});
View
48 frontend/js/bespin/editor/events.js
@@ -44,25 +44,20 @@ bespin.subscribe("bespin:editor:openfile", function(event) {
if (_editSession.checkSameFile(project, filename)) return; // short circuit
- bespin.publish("bespin:editor:openfile:openbefore", { filename: filename });
+ bespin.publish("bespin:editor:openfile:openbefore", { project: project, filename: filename });
_files.loadFile(project, filename, function(file) {
if (!file) {
- bespin.publish("bespin:editor:openfile:openfail", { filename: filename });
+ bespin.publish("bespin:editor:openfile:openfail", { project: project, filename: filename });
} else {
- bespin.publish("bespin:editor:openfile:opensuccess", { file: file });
+ bespin.publish("bespin:editor:openfile:opensuccess", { project: project, file: file });
}
});
});
// ** {{{ Event: bespin:editor:forceopenfile }}} **
//
-// Observe a request for a file to be opened and start the cycle:
-//
-// * Send event that you are opening up something (openbefore)
-// * Ask the file system to load a file (loadFile)
-// * If the file is loaded send an opensuccess event
-// * If the file fails to load, send an openfail event
+// Open an existing file, or create a new one.
bespin.subscribe("bespin:editor:forceopenfile", function(event) {
var filename = event.filename;
var project = event.project;
@@ -107,6 +102,7 @@ bespin.subscribe("bespin:editor:newfile", function(event) {
// TODO: Need to actually check saved status and know if the save worked
bespin.subscribe("bespin:editor:savefile", function(event) {
+ var project = event.project || _editSession.project;
var filename = event.filename || _editSession.path; // default to what you have
bespin.publish("bespin:editor:openfile:savebefore", { filename: filename });
@@ -121,7 +117,7 @@ bespin.subscribe("bespin:editor:savefile", function(event) {
file.lastOp = _editor.undoManager.syncHelper.lastOp;
}
- _files.saveFile(_editSession.project, file); // it will save asynchronously.
+ _files.saveFile(project, file); // it will save asynchronously.
// TODO: Here we need to add in closure to detect errors and thus fire different success / error
bespin.publish("bespin:editor:titlechange", { filename: filename });
@@ -137,6 +133,7 @@ bespin.subscribe("bespin:editor:savefile", function(event) {
// When a file is opened successfully change the project and file status area.
// Then change the window title, and change the URL hash area
bespin.subscribe("bespin:editor:openfile:opensuccess", function(event) {
+ var project = event.project || _editSession.project;
var file = event.file;
var filename = file.name;
@@ -147,17 +144,31 @@ bespin.subscribe("bespin:editor:openfile:opensuccess", function(event) {
bespin.publish("bespin:editor:titlechange", { filename: file.name });
- bespin.publish("bespin:editor:urlchange", { project: _editSession.project, path: file.name });
+ bespin.publish("bespin:url:change", { project: project, path: file.name });
});
// ** {{{ Event: bespin:editor:urlchange }}} **
//
// Observe a urlchange event and then... change the location hash
-bespin.subscribe("bespin:editor:urlchange", function(event) {
- var project = event.project;
- var path = event.path;
+bespin.subscribe("bespin:url:change", function(event) {
+ var hashArguments = dojo.queryToObject(location.hash.substring(1));
+ hashArguments.project = event.project;
+ hashArguments.path = event.path;
+
+ // window.location.hash = dojo.objectToQuery() is not doing the right thing...
+ var pairs = [];
+ for (var name in hashArguments) {
+ var value = hashArguments[name];
+ pairs.push(name + '=' + value);
+ }
+ window.location.hash = pairs.join("&");
+});
- window.location.hash = "project=" + project + "&path=" + path;
+// ** {{{ Event: bespin:url:changed }}} **
+//
+// Observe a request for session status
+bespin.subscribe("bespin:url:changed", function(event) {
+ bespin.publish("bespin:editor:openfile", { filename: event.now.get('path') });
});
// ** {{{ Event: bespin:session:status }}} **
@@ -167,10 +178,3 @@ bespin.subscribe("bespin:session:status", function(event) {
var file = _editSession.path || 'a new scratch file';
self.showInfo('Hey ' + _editSession.username + ', you are editing ' + file + ' in project ' + _editSession.projectForDisplay());
});
-
-// ** {{{ Event: bespin:url:changed }}} **
-//
-// Observe a request for session status
-bespin.subscribe("bespin:url:changed", function(event) {
- bespin.publish("bespin:editor:openfile", { filename: event.now.get('path') });
-});
View
75 frontend/js/bespin/editor/model.js
@@ -31,6 +31,7 @@ dojo.provide("bespin.editor.model");
dojo.declare("bespin.editor.DocumentModel", null, {
constructor: function() {
this.rows = [];
+ this.tabstop = 4; // tab stops every 4 columns; TODO: make this a setting
},
getDirtyRows: function() {
@@ -59,7 +60,7 @@ dojo.declare("bespin.editor.DocumentModel", null, {
// gets the number of characters in the passed row
getRowLength: function(rowIndex) {
- return this.getRowArray(rowIndex).length;
+ return this.getRowString(rowIndex).length;
},
// checks if there is a row at the specified index; useful because getRowArray() creates rows as necessary
@@ -69,6 +70,7 @@ dojo.declare("bespin.editor.DocumentModel", null, {
// will insert blank spaces if passed col is past the end of passed row
insertCharacters: function(pos, string) {
+ pos = this.removeTabSpacesFromPosition(pos);
var row = this.getRowArray(pos.row);
while (row.length < pos.col) row.push(" ");
@@ -119,6 +121,7 @@ dojo.declare("bespin.editor.DocumentModel", null, {
// will silently adjust the length argument if invalid
deleteCharacters: function(pos, length) {
+ pos = this.removeTabSpacesFromPosition(pos);
var row = this.getRowArray(pos.row);
var diff = (pos.col + length - 1) - row.length;
if (diff > 0) length -= diff;
@@ -175,7 +178,7 @@ dojo.declare("bespin.editor.DocumentModel", null, {
// returns the maximum number of columns across all rows
getMaxCols: function() {
var cols = 0;
- for (var i = 0; i < this.rows.length; i++) cols = Math.max(cols, this.rows[i].length);
+ for (var i = 0; i < this.rows.length; i++) cols = Math.max(cols, this.getRowString(i).length);
return cols;
},
@@ -309,6 +312,72 @@ dojo.declare("bespin.editor.DocumentModel", null, {
}
return { row: row, col: col };
- }
+ },
+
+ // returns a string that represents the row; converts tab characters to spaces
+ getRowString: function(row) {
+ var lineText = this.getRowArray(row).join("");
+
+ var tabs = false;
+
+ // check for tabs and handle them
+ for (var ti = 0; ti < lineText.length; ti++) {
+ if (lineText.charCodeAt(ti) == 9) {
+ tabs = true;
+ var toInsert = this.tabstop - (ti % this.tabstop);
+
+ var spacer = "";
+ for (var si = 1; si < toInsert; si++) spacer += " ";
+
+ var left = (ti == 0) ? "" : lineText.substring(0, ti);
+ var right = (ti < lineText.length - 1) ? lineText.substring(ti + 1) : "";
+ lineText = left + " " + spacer + right;
+
+ ti += toInsert - 1;
+ }
+ }
+
+ return lineText;
+ },
+
+ removeTabSpacesFromPosition: function(pos) {
+ var line = this.getRowArray(pos.row);
+ var tabspaces = 0;
+ var curCol = 0;
+ for (var i = 0; i < line.length; i++) {
+ if (line[i].charCodeAt(0) == 9) {
+ var toInsert = this.tabstop - (curCol % this.tabstop);
+ curCol += toInsert - 1;
+ tabspaces += toInsert - 1;
+ }
+ curCol++;
+ if (curCol >= pos.col) break;
+ }
+ if (tabspaces > 0) {
+ return { col: pos.col = pos.col - tabspaces, row: pos.row };
+ } else {
+ return pos;
+ }
+ },
+
+ // returns undefined if the postion is valid; otherwise returns closest left and right valid positions
+ isInvalidCursorPosition: function(row, col) {
+ var line = this.getRowArray(row);
+ var curCol = 0;
+ for (var i = 0; i < line.length; i++) {
+ if (line[i].charCodeAt(0) == 9) {
+ var toInsert = this.tabstop - (curCol % this.tabstop);
+
+ if (col > curCol && col < (curCol + toInsert)) {
+ return { left: curCol, right: curCol + toInsert };
+ }
+
+ curCol += toInsert - 1;
+ }
+ curCol++;
+ }
+
+ return undefined;
+ }
});
View
98 frontend/js/bespin/events.js
@@ -46,16 +46,6 @@ bespin.subscribe("bespin:editor:titlechange", function(event) {
document.title = title;
});
-// ** {{{ Event: bespin:cmdline:executed }}} **
-//
-// Set the last command in the status window
-bespin.subscribe("bespin:cmdline:executed", function(event) {
- var commandname = event.command.name;
- var args = event.args;
-
- dojo.byId('message').innerHTML = "last cmd: <span title='" + commandname + " " + args + "'>" + commandname + "</span>"; // set the status message area
-});
-
// ** {{{ Event: bespin:editor:evalfile }}} **
//
// Load up the given file and try to run it
@@ -72,7 +62,9 @@ bespin.subscribe("bespin:editor:evalfile", function(event) {
_files.loadFile(project, filename, function(file) {
with (scope) { // wow, using with. crazy.
try {
+ bespin.publish("bespin:cmdline:suppressinfo");
eval(file.content);
+ bespin.publish("bespin:cmdline:unsuppressinfo");
} catch (e) {
_commandLine.showInfo("There is a error trying to run " + filename + " in project " + project + ":<br><br>" + e);
}
@@ -80,9 +72,43 @@ bespin.subscribe("bespin:editor:evalfile", function(event) {
}, true);
});
+// ** {{{ Event: bespin:editor:preview }}} **
+//
+// Preview the given file in a browser context
+bespin.subscribe("bespin:editor:preview", function(event) {
+ var filename = event.filename || _editSession.path; // default to current page
+ var project = event.project || _editSession.project;
+
+ // Make sure to save the file first
+ bespin.publish("bespin:editor:savefile", {
+ filename: filename
+ });
+
+ if (filename) {
+ window.open(bespin.util.path.combine("preview/at", project, filename));
+ }
+});
+
+// ** {{{ Event: bespin:editor:closefile }}} **
+//
+// Close the given file (wrt the session)
+bespin.subscribe("bespin:editor:closefile", function(event) {
+ var filename = event.filename || _editSession.path; // default to current page
+ var project = event.project || _editSession.project;
+
+ _files.closeFile(project, filename, function() {
+ bespin.publish("bespin:editor:closedfile", { filename: filename });
+
+ // if the current file, move on to a new one
+ if (filename == _editSession.path) bespin.publish("bespin:editor:newfile");
+
+ bespin.publish("bespin:cmdline:showinfo", { msg: 'Closed file: ' + filename });
+ });
+});
+
// ** {{{ Event: bespin:editor:config:run }}} **
-// w
-// Load the user's config file
+//
+// Load and execute the user's config file
bespin.subscribe("bespin:editor:config:run", function(event) {
bespin.publish("bespin:editor:evalfile", {
project: bespin.userSettingsProject,
@@ -105,6 +131,16 @@ bespin.subscribe("bespin:editor:config:edit", function(event) {
});
});
+// ** {{{ Event: bespin:cmdline:executed }}} **
+//
+// Set the last command in the status window
+bespin.subscribe("bespin:cmdline:executed", function(event) {
+ var commandname = event.command.name;
+ var args = event.args;
+
+ dojo.byId('message').innerHTML = "last cmd: <span title='" + commandname + " " + args + "'>" + commandname + "</span>"; // set the status message area
+});
+
// ** {{{ Event: bespin:commands:load }}} **
//
// Create a new command in your special command directory
@@ -203,41 +239,6 @@ bespin.subscribe("bespin:commands:delete", function(event) {
});
});
-
-// ** {{{ Event: bespin:editor:preview }}} **
-//
-// Preview the given file in a browser context
-bespin.subscribe("bespin:editor:preview", function(event) {
- var filename = event.filename || _editSession.path; // default to current page
- var project = event.project || _editSession.project;
-
- // Make sure to save the file first
- bespin.publish("bespin:editor:savefile", {
- filename: filename
- });
-
- if (filename) {
- window.open(bespin.util.path.combine("preview/at", project, filename));
- }
-});
-
-// ** {{{ Event: bespin:editor:closefile }}} **
-//
-// Close the given file (wrt the session)
-bespin.subscribe("bespin:editor:closefile", function(event) {
- var filename = event.filename || _editSession.path; // default to current page
- var project = event.project || _editSession.project;
-
- _files.closeFile(project, filename, function() {
- bespin.publish("bespin:editor:closedfile", { filename: filename });
-
- // if the current file, move on to a new one
- if (filename == _editSession.path) bespin.publish("bespin:editor:newfile");
-
- bespin.publish("bespin:cmdline:showinfo", { msg: 'Closed file: ' + filename });
- });
-});
-