229 Commits
0.6 ... 0.14.1

Author SHA1 Message Date
Jeff Widman
2f8ec9027b Bump to version 0.14.1 (#227) 2023-12-07 09:31:28 -08:00
Grey Li
ab9a41df6a Add the missing install requirement packaging (#225)
The `packaging` lib is not listed in the `install_requires` list.

https://github.com/greyli/flask-extension-status/actions/runs/7129071225/job/19412328108

```
Run python -c "from flask import Flask; app = Flask(__name__); from flask_debugtoolbar import DebugToolbarExtension; DebugToolbarExtension(app)"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/opt/hostedtoolcache/Python/3.12.0/x64/lib/python3.12/site-packages/flask_debugtoolbar/__init__.py", line 6, in <module>
    from packaging import version as version_builder
ModuleNotFoundError: No module named 'packaging'
Error: Process completed with exit code 1.
```
2023-12-07 08:32:29 -08:00
Jeff Widman
2b1e7d9907 Bump version to 0.14.0 (#223)
We need this to land before we can push a release...
2023-12-06 16:48:19 -08:00
Mac Newbold
d0360218fd Drop CHANGES.rst in favor of GitHub Releases (#198) 2023-11-21 08:26:28 -07:00
Mac Newbold
e9fd3072a9 Merge branch 'master' into switch-to-using-github-auto-generated-releases 2023-11-21 08:24:53 -07:00
Grey Li
1aedfb0e2e Use standard Python gitignore file (#220)
Created from https://github.com/github/gitignore/blob/main/Python.gitignore
2023-11-20 18:29:03 -08:00
Grey Li
e6ae9d0288 Fix the test for basic app (#221)
1. Update the test app

If I understand correctly, the debug toolbar will only be enabled if `app.debug` is `True`. So I added the `DEBUG=True` to the test app.

Related code: 2b8bf9cc44/src/flask_debugtoolbar/__init__.py (L114)

2. Update the `src/flask_debugtoolbar/__init__.py`

Fix the two if statements to prevent the following errors:

```
>       if 'gzip' in response.headers.get('Content-Encoding'):
E       TypeError: argument of type 'NoneType' is not iterable
```

Since the `response.headers.get('Content-Encoding')` could be None.

With this PR, all the tests will be passed. The failed style checker will be fixed in #219
2023-11-20 18:21:38 -08:00
Grey Li
62ce443f8b Fix lint issues and lint config (#219)
For the two ignored rules:

- E731: It's OK to use lambda
- W504: W503 and W504 are conflicts with each other, we need to disable one of them.
2023-11-20 10:59:24 -08:00
Grey Li
2b8bf9cc44 Remove the use of before_first_request (#218) 2023-11-17 22:12:44 +08:00
Grey Li
42d859534a Remove the use of before_first_request 2023-11-17 22:09:25 +08:00
Grey Li
f959951185 Fix tox and GitHub actions settings (#217)
* Remove the branch constraint for pull request triggering
* Use Python 3.12 for main tests
* Use `ubuntu-20.04` for Python 3.6 since it's been removed in `ubuntu-latest`
* Remove Python 2.7 since it's not supported by GitHub Actions anymore (https://github.com/actions/setup-python/issues/672)
* Add the missing `setup-python` step
* Merge the `pip install` commands
2023-11-16 14:41:30 -08:00
dadavec
e1c8704444 Remove deprecated charset property from process_response content crafting (#211)
The `Request.charset` property is deprecated since Werkzeug version 2.3. It was removed in Werkzeug 3.0. Request data must always be UTF-8.

https://werkzeug.palletsprojects.com/en/2.3.x/wrappers/#werkzeug.wrappers.Request.charset
2023-11-15 19:53:02 -08:00
Mac Newbold
8a4cfa5e3c No need to specify custom default value if key not found (#210) 2023-11-15 17:30:39 -07:00
Grey Li
51d105afad Set up GitHub actions to replace Travis (#215) 2023-11-16 02:10:00 +08:00
Grey Li
15192f19e0 Set up github actions 2023-11-15 22:28:43 +08:00
Jeff Widman
5712e57869 No need to specify custom default value if key not found
The `''` arg specifies a custom default value if the key isn't found. However, the default of `None` works fine for boolean testing:

```python
>>> 'gzip' in [None]
False
```

I missed this when I originally reviewed https://github.com/pallets-eco/flask-debugtoolbar/pull/154.
2023-10-13 13:39:35 +00:00
Hiromasa Ihara
9571d06df5 fix: drop response.charset because charset deprecated (#206)
I took a quick peek at upstream to see if this has any backwards-breaking issues whereby we'd need to check the `werkzeug` version, but from my reading it looks it should be fine most of the time.

Also, this was deprecated back in April's `2.3.0` release and then removed in the recent `3.0.0` release, so I suspect most of our userbase will have already migrated to those versions.

Given this lib is a dev-tooling library not typically used in production, I'm not too worried about ensuring we support the 0.01% case where someone is using an old version of `werkzeug` + a custom charset.

More context:
* https://github.com/pallets/werkzeug/issues/2602
* https://github.com/pallets/werkzeug/pull/2641
* https://github.com/pallets/werkzeug/pull/2768
2023-10-13 06:35:25 -07:00
Nick Janetakis
3b25e114e9 fix: use urllib.parse.quote_plus and drop werkzeug.urls.url_quote_plus (#207) 2023-10-06 18:19:51 -04:00
Hiromasa Ihara
f7ae3fd591 fix: use urllib.parse.quote_plus and drop werkzeug.urls.url_quote_plus 2023-10-01 16:03:15 +09:00
Hiromasa Ihara
ce02d2da3c fix: migrate from deprecated flask.Markup to markupsafe.Markup (#203) 2023-05-24 10:22:56 -07:00
Jeff Widman
bd346a0fc1 Drop CHANGES.rst in favor of GitHub Releases
I started to cut a new release but realized it's a bit painful that we
are currently maintaining both a `CHANGES.rst` file and also tagging
releases in GitHub releases UI.

For a busy project, maintaining a dedicated changelog makes sense... but
the reality is this project is in maintenance mode, and we the
maintainers are more likely to cut releases when they're easy/low
friction. Since GitHub very nicely has the "Auto-generate-release-notes"
button, let's just use that.

I considered copy/pasting the results from that to `CHANGES.rst`, but
even that feels like a waste of time.
2023-01-17 20:05:25 +00:00
Jeff Widman
ed8243e17e Point URLs at pallets-eco/flask-debugtoolbar (#197) 2023-01-17 08:16:30 -08:00
Nick Janetakis
02c99a7b64 Fix Flask SQLAlchemy quickstart link (#196) 2022-12-27 08:10:52 -05:00
Francesco Frassinelli
96514793e4 Update sqlalchemy_error.html 2022-12-27 13:10:16 +01:00
Francesco Frassinelli
ec8cc3a0ba Fix Flask SQLAlchemy quickstart link 2022-12-27 10:54:53 +01:00
Jeff Widman
e3c8ab0ca2 Point at new location of django-debug-toolbar (#189) 2022-11-02 12:53:49 -07:00
Jeff Widman
fefb32b04d Fix outdated docs links (#187) 2022-11-02 12:51:06 -07:00
Nick Janetakis
b5a7c032ab Merge pull request #186 from Dosenpfand/flask-sqlalchemy-v3
Flask-SQLAlchemy 3 compatibility
2022-10-28 11:41:38 -04:00
Dosenpfand
6af24f5f44 Do not explicitly set SQLALCHEMY_TRACK_MODIFICATIONS for test/example 2022-10-28 11:28:50 +02:00
Dosenpfand
b4a197f87f Enable query recording if debug is active, remove warning 2022-10-26 14:31:14 +02:00
Dosenpfand
15b6fee933 Fix typo 2022-10-24 21:29:59 +02:00
Dosenpfand
9e03576c94 Keep config for flask_sqlalchemy < 3 2022-10-24 19:35:47 +02:00
Dosenpfand
bfa48c5a2c Add support for flask_sqlalchemy >= 3.0.0 2022-10-24 18:59:28 +02:00
sur.la.route
890fd9c7fb updated to work with flask 2.2+ (#183)
Replaced `_request_ctx_stack.top` with `request_ctx` per https://github.com/pallets/flask/pull/4682

This checks the version of `flask` and only does the switcheroo on newer versions of flask...
2022-10-04 16:20:14 -07:00
Hugo van Kemenade
42a9651950 Replace deprecated threading.currentThread with threading.current_thread (#179) 2022-09-18 14:08:43 -07:00
Nick Janetakis
ff01910f6a Merge pull request #182 from caffeinatedMike/master
Fixed scrollbar issues
2022-08-18 11:20:45 -04:00
Michael Hill
9cdb04edcb Make flDebugToolbar vertically scrollable
When on small screens where the debug toolbar is longer than the screen there was no way to access the items listed at the bottom of the toolbar. Adding `overflow-y: auto` allows a scrollbar to be used when this is the case.
2022-08-02 09:41:44 -04:00
Michael Hill
f546d4633b Fixed classname and bottom padding for scrollbar 2022-07-26 19:08:25 -04:00
Michael Hill
db07028aa0 Update scroll classname to make base.html template 2022-07-26 19:07:03 -04:00
Nick Janetakis
d44ab40729 Merge pull request #180 from timgates42/bugfix_typos
docs: Fix a few typos
2022-07-13 17:03:28 -04:00
Tim Gates
0d409f54f5 docs: Fix a few typos
There are small typos in:
- src/flask_debugtoolbar/panels/profiler.py
- src/flask_debugtoolbar/static/codemirror/mode/rst/rst.js
- src/flask_debugtoolbar/static/codemirror/mode/xquery/xquery.js
- src/flask_debugtoolbar/static/js/jquery.tablesorter.js

Fixes:
- Should read `second` rather than `secound`.
- Should read `preceded` rather than `preceeded`.
- Should read `initial` rather than `inital`.
- Should read `divided` rather than `divded`.
- Should read `debugging` rather than `debuging`.
- Should read `convenience` rather than `conveinence`.
- Should read `capabilities` rather than `capabilitys`.
2022-07-14 06:34:03 +10:00
Nate Collins
708df1c07a Expand HTTP codes on which the toolbar will be displayed (#176)
Change to expand what HTTP codes the toolbar displays on. Currently it only displays on 200 (or redirects when DEBUG_TB_INTERCEPT_REDIRECTS is enabled). However, debugging or verifying non-200 pages is commonly necessary. This change would enable display of the toolbar on other pages that may serve HTML, specifically: 201, 400, 401, 403, 404, 405, 500, 501, 502, 503, 504
2022-04-01 13:32:48 -07:00
Nathan Collins
10186a4202 Permit scrolling for content panels 2022-04-01 13:30:42 -07:00
Nate Collins
3cd2dceace Add ARIA role to toolbar for accessibility improvement (#174)
Change to improve accessibility compliance, for example useful when a developer is using a screen reader. Using the ARIA role attribute is a HTML4 compatible way of accomplishing this.

For reference: https://dequeuniversity.com/rules/axe/4.3/region

Co-authored-by: Megan Schanz <schanzme@msu.edu>
2022-04-01 13:30:16 -07:00
Megan Schanz
376c3deab3 Submodule to use https protocol after unencrypted git proto deprecated
Ref: https://github.blog/2021-09-01-improving-git-protocol-security-github/
2022-04-01 13:29:24 -07:00
Nick Janetakis
45d3588bb6 Release 0.13.1 2022-03-29 18:39:20 -04:00
Nick Janetakis
15e8d77a49 Fix changelog formatting for 0.12.X 2022-03-29 18:10:40 -04:00
Nick Janetakis
4d84f262ae Merge pull request #172 from flask-debugtoolbar/fix-setupcfg
Move library into src/ directory
2022-03-29 18:08:01 -04:00
Nick Janetakis
db64ce632c Move library into src/ directory
After building this locally I noticed all of the expected files were in
the wheel where as before this patch it was missing a lot of files.

This idea of using a src/ directory is mentioned in the official Python
documentation for packaging files at:

https://packaging.python.org/en/latest/tutorials/packaging-projects/

It's also used in Flask and other large Python projects.
2022-03-29 08:18:21 -04:00
Jeff Widman
956d7501ec Bump version for development 2022-03-28 01:19:17 -07:00
Jeff Widman
a758a9df7a Release 0.12.1 2022-03-28 01:16:17 -07:00
Jeff Widman
5eea25882c Fix changelog / docs link
I fat-fingered this in
https://github.com/flask-debugtoolbar/flask-debugtoolbar/pull/164 so
correcting it.
2022-03-28 01:14:12 -07:00
Jeff Widman
e954cd9fae Bump version for development 2022-03-27 23:48:24 -07:00
Jeff Widman
03d79be02c Release 0.12.0 2022-03-27 23:48:08 -07:00
Jeff Widman
7f17d2ce57 Update PyPI metadata files: add setup.cfg etc (#164)
Update to the newer PyPI / python packaging metadata file structure.

A lot of this was cribbed from how [`Flask`](https://github.com/pallets/flask) itself
exposes its metadata.
2022-03-26 21:51:54 -07:00
Nick Janetakis
30fba11f36 Remove with_ extension for Jinja 3.0 (#157)
Jinja 3.0 throws a deprecation warning that this will be removed in 3.1
because it's built into Jinja now without needing an extension.

However since folks might want to use Jinja 2 for a while this supports
both versions by only using the extension with Jinja 2.
2022-03-25 21:26:26 -07:00
jnnkB
d474a6a689 prefixed css classes, fixes #152 (#153) 2020-08-14 11:39:15 -07:00
zaw007
83d398d9d5 Support gzip response (#154)
* add gzip compress and decompress

* support gzip response
2020-08-14 11:37:37 -07:00
Jeff Widman
3929742d9c Cleanup version handling slightly (#149)
Some improvements I saw over on
https://github.com/FactoryBoy/factory_boy/pull/676/files that looked
useful here as well.
2020-03-09 13:10:02 -07:00
Jeff Widman
70abd78e55 Setup DB properly
When I switched over to `flask run` entrypoint in b92391d177,
I forgot that the `if name==__main__` code no longer triggers.
So the SQLite in-memory database wasn't getting created for the example
app.

This moves the DB creation to a werkzeug/Flask hook that runs before the
first request to the app, so that the DB table is created when we query
it.

Also updated the test which worked fine previously, but this is more
idiomatic.
2020-03-09 10:01:59 -07:00
Jeff Widman
dbea74b626 Update README.rst 2020-03-09 09:46:05 -07:00
Jeff Widman
c6102aeb14 Change docs to pull version from setup.py 2020-03-02 09:37:37 -08:00
Jeff Widman
10c9c1ae5d Update Flask-SQLAlchemy links 2020-02-22 20:39:21 -08:00
Yaser Amiri
9e600c6e13 Add flask.g section to show g object content. 2020-02-22 09:47:21 -08:00
Matthew Swabey
9b8a8afa97 Fix SQLAlchemy SELECT/EXPLAIN to use url_for to respect app prefixes. Provide url_for to all toolbar templates 2020-02-22 09:24:23 -08:00
Jeff Widman
39ac97a7e0 pycodestyle fixes 2020-02-18 01:21:40 -08:00
Jeff Widman
a5cb5a709f Bump version for development 2020-02-18 01:19:49 -08:00
Jeff Widman
02064c76ed Release 0.11.0 2020-02-18 01:11:19 -08:00
Jeff Widman
d713732807 Cleanup tox/travis
* Switch to python 3.8 in Travis. I tried to add 3.8 while keeping 3.7
and 3.6, but ran into issues with Travis config, so instead just bumped
straight to 3.8. Long term I'd like to explore moving to Azure
Pipelines, but don't have the time to figure that out just yet.
* `flake8` was renamed to `pycodestyle`
* `py.test` was deprecated in favor of `pytest`
2020-02-18 00:42:39 -08:00
Jeff Widman
b92391d177 Switch to Flask's native CLI
Drop `flask_script` in favor of Flask's native CLI:
* https://flask.palletsprojects.com/en/master/cli/

This also requires changing the tests so that `pytest` mocks the env var
`FLASK_ENV` so that the test app starts in development mode. Unlike
normal test apps, we _do_ want development/debug mode, in addition to
testing mode.
2020-02-17 23:57:04 -08:00
Florian
4964ae261f RoutesList: Do not show debugtoolbar routes 2020-02-17 22:30:10 -08:00
Pierre GIRAUD
ad847299c4 Add doc for SQL syntax highlighting 2020-02-17 21:57:01 -08:00
Jeff Widman
7ce099c3d0 Remove deprecated request.is_xhr
This was removed from `werkzeug` `1.0.0`.

Details:
* https://github.com/pallets/werkzeug/issues/1077
* https://github.com/pallets/werkzeug/issues/1714 (search for `is_xhr`)

Fix #144.
2020-02-17 21:50:56 -08:00
Jeff Widman
9c7db48362 Explicitly disable SQLALCHEMY_TRACK_MODIFICATIONS
This silences deprecation warnings.
Background: https://stackoverflow.com/a/33790196/770425

Note: This code can be removed once `flask_sqlalchemy` 3.0 ships, or any
release that includes
https://github.com/pallets/flask-sqlalchemy/pull/727.
2020-02-17 21:36:01 -08:00
Tim Gates
88f15cba35 Fix simple typo: exapanded -> expanded
Closes #141
2020-02-06 14:03:36 -08:00
Matt Good
d852042ccb Merge pull request #119 from davidism/json-available
don't use flask.json_available
2018-02-07 09:52:11 -08:00
David Lord
5bd2e8a423 flask.json_available is a no-op
it is removed in Flask 1.0
2018-02-07 07:13:21 -08:00
Jeff Widman
c27256c00a Bump dev version 2017-02-12 03:33:05 -08:00
Jeff Widman
010206298e Release 0.10.1 2017-02-12 03:04:07 -08:00
Jeff Widman
678ec31229 Revert "Change docs to pull version from setup.py"
This reverts commit 45b70c952f.
This commit broke the readthedocs build because RTD is building the docs
without running setup.py. So there is no package metadata.
2017-02-12 02:48:44 -08:00
Jeff Widman
23d52703fd Make copyright string track current year 2017-02-12 02:33:47 -08:00
Jeff Widman
45b70c952f Change docs to pull version from setup.py 2017-02-12 02:32:47 -08:00
Jeff Widman
9f2c353e86 Trivial imports cleanup 2017-02-12 02:31:26 -08:00
Jeff Widman
cfe142b285 Point Flask-SQLAlchemy docs at ‘latest’ 2017-02-12 00:21:20 -08:00
Jeff Widman
19a25fd895 Add universal wheel support
Let’s roll!
2017-02-10 01:09:59 -08:00
Jeff Widman
8dcb97c05a Travis should use python 3.6 interpreter 2017-02-10 00:50:38 -08:00
Jeff Widman
b49af327cf Use containers for travis tests 2017-02-10 00:44:27 -08:00
Jeff Widman
38ab3a49c1 Test using modern python 2017-02-10 00:42:00 -08:00
Jeff Widman
9f901e34ae Remove deprecated tox arg —use-mirrors 2017-02-10 00:36:19 -08:00
Jeff Widman
f050a6b0de Use latest for flask-sqlalchemy docs rather than pinning version 2017-02-10 00:28:45 -08:00
Jeff Widman
e33f3e1c85 Update RTD links w/https & .io 2017-02-09 23:54:16 -08:00
Peter M. Landwehr
02ff95acde Add LICENSE file to MANIFEST.in / source bundle (#102) 2016-09-18 23:13:08 -07:00
Iuri de Silvio
2436239964 Additional cleanup of deprecated flask.ext.* magic imports. (#97) 2016-06-29 16:52:23 -07:00
Michael Lenzen
18a0030354 Fix deprecated import from flask.ext.sqlalchemy (#94) 2016-06-29 13:33:02 -07:00
Matt Good
ee726eece2 Better setup.py description 2015-04-17 17:53:40 -07:00
Matt Good
1cd9ba69d1 Update CHANGES for 0.10 2015-04-17 17:49:30 -07:00
Matt Good
30a1cd399a Add docs note on Versions panel displaying other packages 2015-04-17 17:49:30 -07:00
Matt Good
0524e7f3e9 Update "Thanks" section of docs 2015-04-17 17:49:30 -07:00
Matt Good
14cd912df7 Fix style errors and add to automated checks
Adds a "stylecheck" to the Tox build config to automatically run flake8 checks.
Fixes existing flake8 issues.
2015-04-17 14:23:58 -07:00
Matt Good
f087311f7c Add missing sqlalchemy_error template
Missed adding this file in the last commit.
2015-04-17 14:19:41 -07:00
Matt Good
61d1fc2678 Display steps needed to display SQL queries
In the SQLAlchemy panel, detect if Flask-SQLAlchemy is not set up to record
queries for the current app, and display the necessary steps to set it up.

The following steps will be detected and displayed if needed:

* install Flask-SQLAlchemy package
* add the extension to this app
* set SQLALCHEMY_RECORD_QUERIES if DEBUG is False
2015-04-17 13:51:10 -07:00
Matt Good
6b2566c01f Enable toolbar on HTML5 pages without </body> tag
Includes the toolbar on HTML5 pages without an explicit </body> tag by checking
for the HTML5 `<!doctype html>`.

Fixes #79
2015-04-17 12:56:10 -07:00
Matt Good
31ea3bce41 Merge branch 'body-warning' 2015-04-16 14:54:32 -07:00
Matt Good
2d60ea6c8d Fix case-insensitive HTML insertion
Keeps the case-insensitive search for "</body>" and removes extra scan for the
tag by trying to insert the toolbar, and warning if unsuccessful.
2015-04-16 14:47:46 -07:00
Matt Good
56994a522d Document using init_app() for the factor pattern 2015-04-16 14:24:24 -07:00
Matt Good
aa90a9d052 Remove restyling table rows before sorting
It doesn't seem to be necessary to remove the "even / odd" styles before
sorting, since they are explicitly toggled to the right state after sorting. It
should be slightly more responsive to skip this step when sorting large tables.
2015-04-15 12:47:14 -07:00
Matt Good
32e0aa8ca3 Ensure profiler "Calls" are sorted numerically
JS Tablesorter's detection of the data type is occasionally confused when the
profiler calls show ratios like "94/57" for total calls to primitive calls.
This change enables parsing "data" attributes on the table headers to pass as
options to Tablesorter. This way we can explicitly specify to use the "digit"
sorter which works the way we want.

Fixes #62
2015-04-15 12:39:59 -07:00
Matt Good
e052b005ec Merge branch 'package-versions' 2015-04-14 19:21:45 -07:00
Matt Good
2f18ac90f9 Convert versions template to 2 spaces for indentation 2015-04-14 19:20:29 -07:00
Matt Good
612f93c129 Show relative paths for package paths
Since most of the paths are the same, only display the path relative to the
main Python library path.
2015-04-14 19:18:45 -07:00
Matt Good
c0826c9a31 Add detailed docs on the built-in panels 2015-04-14 17:22:07 -07:00
Matt Good
7e2de8f588 Merge pull request #48 from yoloseem/sqlparse
Suggestion: Use sqlparse.format() for better sql indentation
2015-04-14 15:44:00 -07:00
Matt Good
5aaa7a6634 Merge branch 'routes' 2015-04-14 15:28:16 -07:00
Matt Good
f7feecc751 Include the "static" route in the list
While this is enabled for most apps, it's optional and might still be useful to display.
2015-04-14 15:23:04 -07:00
Matt Good
cfe1624730 Simplify route methods display
Show just the names as text, without the Python list syntax formatting.
2015-04-14 14:58:31 -07:00
Matt Good
95d7eb977f Format route_list template with spaces 2015-04-14 14:57:47 -07:00
Matt Good
794380fc03 Merge pull request #81 from EricWorkman/master
Add tablesorter to slqlalchemy panel
2015-04-14 14:46:40 -07:00
Eric Workman
5a05620257 Add tablesorter to slqlalchemy panel 2015-04-10 15:44:38 -04:00
Matt Good
bebe884615 Update for 0.9.2 release 2014-12-05 15:43:46 -08:00
Matt Good
10c03880c7 Safely decode non-ascii SQL queries for display
SQL queries containing non-ascii byte strings would cause errors, both with and
without Pygments highlighting.

This updates the non-Pygments case to handle a simple decoding to ensure the
value is ascii-safe. It also removes passing an explicit "utf-8" encoding to
Pygments, since this causes errors when the bytes are not utf-8. When the
encoding is omitted, Pygments will default to "guess" the encoding by trying
utf-8 and falling back to latin-1.

Fixes #55
2014-12-04 14:06:48 -08:00
Matt Good
3fcdfc8f83 Use platform-sensitive filename normalization
The os.path.commonprefix() function only does basic string prefix checking, and
isn't aware of case-insensitive file names, or path separators. Instead, this
switches the filename formatting to use os.path.relpath() for normalizing paths
relative to sys.path.

Fixes #67
2014-12-02 18:15:59 -08:00
Matt Good
914553ddf5 Consolidate redundant code for SQL select/explain
The code for displaying the SQL select results and explain plan were nearly
identical, so this consolidates it to one function and template. It also fixes
correctly displaying the timing results as milliseconds.
2014-11-24 15:04:50 -08:00
Matt Good
b4fe0b954c Fix misspelled CSS classes 2014-11-24 14:46:41 -08:00
Matt Good
7557ee6794 Ensure SQL queries are HTML-escaped
The SQL queries were displayed with the `safe` filter which allowed properly
including the Pygments-highlighted HTML, but if Pygments wasn't installed this
allowed the raw SQL to be included without escaping. This change removes the
`safe` filter and instead wraps the Pygments HTML with the `Markup` class. This
allows proper auto-escaping in the template.

Fixes #70
2014-11-24 14:36:56 -08:00
Matt Good
382c5e7da9 Remove old commented code
Looks like some old code from the original Django toolbar was left in the
template.
2014-11-24 14:25:32 -08:00
Matt Good
6286dadc27 Version update for 0.9.1 release 2014-11-24 13:25:17 -08:00
=
81f8e34846 Removed printing of routes to console. 2014-03-23 15:14:28 +11:00
=
79717926a5 Added endpoints, HTTP methods, Is alias and Redirect to columns to route list. 2014-03-23 15:12:31 +11:00
Justin McKay
5084428c9d Added the Route List panel to show the routes that are available within Flask. 2014-03-22 23:57:48 +11:00
Matt Good
82295aa4aa printable filter should replace non-ascii bytes
In Python 2, when repr() returns bytes, replace any non-ascii bytes
with the unicode ? character to ensure that the result is printable.

Fixes #66
2014-02-02 14:46:41 -08:00
Matt Good
70488fc14a Fix Py3 support for bytes SQL queries
Fixes #64
2014-01-14 17:56:47 -08:00
Matt Good
4901d68b01 Fix version number in docs 2014-01-03 15:14:49 -08:00
Matt Good
d671a849bb Bump version for new release 2014-01-03 15:07:48 -08:00
Matt Good
ffbdf0f563 Add SQLAlchemy to test app 2013-12-31 17:55:19 -05:00
Matt Good
b08fe477b6 Merge pull request #63 from defuz/pep8
PEP8 formatting
2013-12-02 16:20:27 -08:00
Ivan Ivaschenko
903ed85f00 remove coding:utf-8 and add some style fixes 2013-11-29 00:17:15 +02:00
Ivan Ivaschenko
5710314252 pep8 formatting + coding: utf8 2013-11-28 18:31:29 +02:00
Matt Good
277462d822 Fix build status image
RST example copied from Travis was a bit broken
2013-11-06 09:59:50 -08:00
Matt Good
26691d49cd Add Travis build statis to README 2013-11-06 09:58:49 -08:00
Matt Good
bd37db9dc4 Fix SQLAlchemy URL registration
The init_app() restructuring in 356e6c removed "load_panels()", but need
to add it back so panels like SQLAlchemy can register their URLs on the
Blueprint before it's activated.
2013-11-06 09:33:00 -08:00
Matt Good
091c4fa70c Compatibility fixes for Python 3
Fixes #54 (at least the basic stuff, this is still a bit experimental)
2013-11-06 09:06:05 -08:00
Matt Good
d5987410d1 Add basic sanity test 2013-11-06 09:00:05 -08:00
Matt Good
6d8249160d Rename "settings" panel to "config" 2013-11-01 14:04:10 -07:00
Matt Good
1e046d57f4 Remove some unneeded code from SettingsVars panel 2013-11-01 12:42:38 -07:00
Matt Good
9e3546b416 Merge commit 'refs/pull/51/head' of github.com:mgood/flask-debugtoolbar
Conflicts:
	flask_debugtoolbar/toolbar.py
2013-11-01 11:41:08 -07:00
Matt Good
26034475f2 Load panels with werkzeug's "import_string"
Also add back caching of the loaded panel classes.
This prevents repeated warnings if there was an import error loading the
class the first time.
2013-07-30 17:56:54 -07:00
Matt Good
356e6c8268 Restructure to support init_app
Fixes #38
2013-07-30 17:37:34 -07:00
Matt Good
5870fce178 Can check blueprint name instead of URL prefix 2013-07-30 09:25:02 -07:00
Alexey Diyan
855c7ab377 Port SettingsVarsDebugPanel from Django DebugToolbar 2013-05-10 19:17:32 +03:00
Lucas Taylor
ccd8ba66b2 Display installed packages and versions in Versions panel (requires setup tools) 2013-03-27 17:28:51 -07:00
Lucas Taylor
093763909f Display installed packages and versions in Versions panel (requires Yolk) 2013-03-27 17:16:28 -07:00
Matt Good
115e874a39 Reformat templates and static files with 2 space indentation
Fixes #45
2013-03-16 16:21:39 -07:00
Hyunjun Kim
bd2b65d068 Improve format_sql(): use sqlparse.format() 2013-03-11 13:33:41 +09:00
Matt Good
b7c39113e7 Template editor should use Jinja loader's encoding
The template loader wouldn't handle non-ASCII template encodings.
Updated it to use the encoding parameter of the Jinja loader when
reading and writing the template files.

Fixes #46
2013-03-08 09:00:10 -08:00
Matt Good
05288daf17 Docs Makefile needs to ensure git submodules are initialized 2013-02-21 13:43:27 -08:00
Matt Good
79421df5cd Use Flask theme for the documentation 2013-02-21 13:37:12 -08:00
Matt Good
8d5d37fa0b Merge branch 'release/0.8.0' 2013-02-21 12:26:23 -08:00
Matt Good
1029b9131b Prepare 0.8.0 release 2013-02-21 12:25:45 -08:00
Matt Good
3bea63dc8a Fix werkzeug request logging with the log panel
Werkzeug will disable its default logging setup if another log handler
is already configured.  At some point the initialization order changed
and the logging panel's handler is getting added first now, so
werkzeug's request log will not be printed to the console by default. By
explicitly calling werkzeug's logger we now make sure it's initialized
before the logging panel's handler.

Fixes #33
2013-02-21 10:53:16 -08:00
Matt Good
856eb52a95 Nice error message for un-repr-able objects
The "printable" filter can throw an exception when trying to display a
user object whose __repr__ implementation returns unicode, which repr()
does not allow. Catch it and display a more informative message
instead.

See #31 and #39
2013-02-20 20:46:31 -08:00
Matt Good
50017f13a7 Switch to itsdangerous for SQL query signing
Fixing #31 where the query signing would break when the SECRET_KEY
contained non-ascii values. In the process switch to itsdangerous
instead of rolling our own signatures.
2013-02-20 20:02:00 -08:00
Matt Good
561889738f Merge pull request #41 from crosspop/ajaxfix
Intercept redirect only if it’s not XHR (Ajax)
2013-02-19 17:01:11 -08:00
Matt Good
b97e58c1f2 Merge pull request #42 from bollwyvl/share-jquery
adding reference to noConflict'ed jQuery
2013-02-19 11:10:48 -08:00
Nicholas Bollweg
0f923475c8 adding reference to noConflict'ed jQuery 2013-02-13 12:29:45 -05:00
Hong Minhee
43df69ab24 Intercept redirect only if it's not XHR (Ajax)
Intercept redirect can't really help debugging at all
if requests are XHR (Ajax), because browsers just don't render
their response by default.
2013-01-17 05:11:26 +09:00
Matt Good
3e4cd1ecfa Merge pull request #35 from rconradharris/patch-1
Profiler: Use update_wrapper on partial

If the profiler panel is applied before the request-vars panel, the wrapper added by the profiler causes an error since it's missing a `__module__` attribute.
2012-09-25 10:43:19 -07:00
Matt Good
cfa5984d3e Convert SQLAlchemy query duration to ms to match the label
The SQLAlchemy panel labels the query duration column "(ms)" but the
times from Flask-SQLAlchemy are given in seconds.

Fixes #36
2012-09-25 09:45:52 -07:00
Rick Harris
c9c0228a62 Profiler: Use update_wrapper on partial
The `update_wrapper` call is needed so that the new partial
func inherits the `__module__` of the original function.

The `__module__` is needed because the request-vars panel uses
`func.__module__` in building the qualified name.
2012-09-04 19:45:51 -05:00
Matt Good
d87e7fc347 Fix version number in docs 2012-05-19 18:22:18 -07:00
Matt Good
80eb747816 Release 0.7.1 to fix the in-place loading of the template editor 2012-05-18 18:20:28 -07:00
Matt Good
913c4130b6 Prepare 0.7 release 2012-05-18 17:54:46 -07:00
Matt Good
ffed47e157 Document DEBUG_TB_PROFILER_ENABLED config option 2012-05-18 17:23:12 -07:00
Matt Good
c7b0b4bf2b Merge remote-tracking branch 'evoloshchuk/enable_profiler_panel' into enable_profiler_panel 2012-05-18 17:18:25 -07:00
Matt Good
defbaefb44 Add template editor note to CHANGES.rst 2012-05-18 17:17:24 -07:00
Matt Good
18414d733b Add a live template editor to the template panel 2012-05-18 16:32:03 -07:00
Matt Good
83523d6f59 Update example.py to use flask.ext compatibility imports. 2012-05-09 15:18:55 -07:00
Matt Good
97fc95f769 Merge branch 'profile-sorter' 2012-04-17 08:33:26 -07:00
Matt Good
484575eb66 Make striped table rows work with table sorter 2012-04-17 08:33:06 -07:00
Matt Good
e34c10d15b Simplifying CSS resets 2012-04-17 08:05:16 -07:00
Matt Good
025a7bebb6 Use spaces for indentation in CSS/JS 2012-04-17 08:04:05 -07:00
Matt Good
bada1ae429 Merge pull request #24 from evoloshchuk/sqlalchemy_sql_select
Didn't find the original problem, seem to work fine.
2012-04-17 07:46:49 -07:00
Ievgen Voloshchuk
b7a67a0486 Didn't find the original problem, seem to work fine. 2012-04-17 10:13:16 +02:00
Ievgen Voloshchuk
addfb343b0 let's use tabs if all file use them 2012-04-17 09:54:40 +02:00
Ievgen Voloshchuk
11cb1daadf css selector corrections 2012-04-17 09:53:16 +02:00
Andrii Mishkovskyi
ddeaa2b958 Correctly try to sort all the tables possible 2012-04-17 09:50:42 +02:00
Ievgen Voloshchuk
9c1d859cfd Make profiler results sortable 2012-04-17 09:50:20 +02:00
Ievgen Voloshchuk
d927012d75 Add configuration parameter to enable profiler by default 2012-04-17 09:49:05 +02:00
Matt Good
72e8e277af Bump version to 0.6.3.1 to re-release with changelog info 2012-04-16 13:25:25 -07:00
Matt Good
75bac4e961 Add changelog info for 0.6.3 2012-04-16 13:21:25 -07:00
Matt Good
f84087156a Prepare 0.6.3 release 2012-04-16 13:15:26 -07:00
Matt Good
56be56de86 Fix import for Flask-SQLAlchemy 0.16
Use the "flask.ext" import for compatibility with old and new versions
2012-04-16 13:12:26 -07:00
Rune Halvorsen
f6e37be73c Issue a warning, rather than an exception, when toolbar can't be inserted 2012-03-10 19:36:57 +01:00
Rune Halvorsen
71bd15a4d6 Added an exception when debug toolbar can't be inserted due to missing markup 2012-02-29 01:44:17 +01:00
Matt Good
7e8a8e280e Merge pull request #18 from mattoufoutu/master
display context variables values in the "templates" panel
2012-02-23 09:23:22 -08:00
Mathieu D. (MatToufoutu)
672fdb59e6 remove unused outer_loop variable 2012-02-23 18:15:16 +01:00
Mathieu D. (MatToufoutu)
b5a6fbe839 use dictsort instead of items() to display variables alphabetically ordered 2012-02-23 18:14:02 +01:00
Mathieu D. (MatToufoutu)
824f73d9aa minor fixes in the templates 2012-02-23 14:40:02 +01:00
Mathieu D. (MatToufoutu)
d8d2fb7a85 display context variable values in the templates panel 2012-02-23 14:33:42 +01:00
Matt Good
8a922a2a5c Cookie for list of "active" panels needs decoded before splitting 2012-02-20 14:14:53 -08:00
Matt Good
ba56f81d19 Preparse 0.6.2 release 2012-02-18 14:22:04 -08:00
Matt Good
002dea199e Simplify jQuery.noConflict mode to fix #16 2012-02-18 14:18:41 -08:00
Matt Good
bb99b48833 Remove trailing slashes in MANIFEST.in since Windows doesn't like them
(fixes #17)
2012-02-18 13:41:17 -08:00
Matt Good
210dfb48fc Link from docs to GitHub project 2012-02-15 18:54:12 -08:00
Matt Good
7e83d45d6c Add some basic Sphinx docs (fixes #12) 2012-02-15 18:49:03 -08:00
Matt Good
6d268ba00b Add skeleton for Sphinx docs (re #12) 2012-02-15 18:15:41 -08:00
Matt Good
e92c102bc9 Prepare 0.6.1 release 2012-02-15 12:35:58 -08:00
Matt Good
20a302c3b4 Fix request vars display for non-string values (fixes #14) 2012-02-15 12:28:28 -08:00
Matt Good
bdbc570c91 Fix memory leak from holding reference to toolbar (fixes #10) 2012-02-14 23:56:36 -08:00
Matt Good
17b629742d Add protocol to django-debug-toolbar link in readme 2012-02-14 19:17:49 -08:00
Matt Good
54a18b8cf2 Improve the readme example (fixes #8) 2012-02-14 19:14:37 -08:00
Matt Good
a48efe822a Escape non-printable values in request vars panel (fixes #9) 2012-02-14 18:51:21 -08:00
Matt Good
45ce65c058 Merge branch 'mono-fonts' 2012-01-31 11:03:38 -08:00
Matt Good
89fbe2e6b5 Let DEBUG_TB_ENABLED take precedence over app.debug 2012-01-31 11:02:30 -08:00
Matt Good
e5feff5884 Merge pull request #7 from joeshaw/joeshaw-enabled-setting
add DEBUG_TB_ENABLED setting which overrides app.debug check
2012-01-31 10:59:32 -08:00
Matt Good
93008a8e53 Make table bodies use mono-spaced font 2012-01-31 10:52:55 -08:00
Matt Good
8bb829e1e4 Make font reset easier to override 2012-01-31 10:25:17 -08:00
Matt Good
70478ffcc2 Merge pull request #5 from kennethreitz/patch-1
Requiring setuptools is evil :)
2012-01-31 00:50:27 -08:00
Joe Shaw
6f6b1cb47d add DEBUG_TB_ENABLED setting which overrides app.debug check 2012-01-30 23:41:43 -05:00
Kenneth Reitz
1834443246 Requiring setuptools is evil :) 2012-01-30 22:26:37 -05:00
Matt Good
c3c2714ace Merge pull request #3 from kennethreitz/readme
Updated Readme
2012-01-30 17:50:38 -08:00
Kenneth Reitz
884451669c (sp) 2012-01-30 20:48:03 -05:00
Matt Good
0bdc3f7e59 Merge pull request #2 from kennethreitz/logo
Flask Logo
2012-01-30 17:47:45 -08:00
Kenneth Reitz
43b069905d readme fix 2012-01-30 20:46:41 -05:00
Kenneth Reitz
d85ce273af squash all the PNGs 2012-01-30 20:42:00 -05:00
Kenneth Reitz
69d137c2d7 new django flask icon 2012-01-30 20:41:50 -05:00
Kenneth Reitz
7988e5f472 new read me 2012-01-30 20:29:57 -05:00
Matt Good
1c8465cdd9 Minor style update (trailing whitespace and "if len()") 2012-01-04 08:49:25 -08:00
Matt Good
f59705a7e7 Merge commit 'd886f5a606f26d78df457e7041c5666e0e5bbc6d' 2012-01-04 08:42:21 -08:00
Dmitry Ishutkin
d886f5a606 add hosts setting
app.config['DEBUG_TB_HOSTS']

Example: app.config['DEBUG_TB_HOSTS'] = ( '127.0.0.1', '::1' )

If the request’s REMOTE_ADDR is not in this list, the toolbar will not
be displayed and the exception handler will not be active. This should
be a tuple. Default: (). Empty list remains backward compatibility. If
DEBUG_TB_HOSTS not specified in app.config, the debug toolbar is shown
for all addresses.
2011-09-30 18:30:56 +08:00
238 changed files with 24001 additions and 1669 deletions

41
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Tests
on:
push:
branches:
- master
paths-ignore:
- 'docs/**'
- '*.rst'
pull_request:
paths-ignore:
- 'docs/**'
- '*.rst'
jobs:
tests:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {name: Linux, python: '3.12', os: ubuntu-latest, tox: py312}
- {name: Windows, python: '3.12', os: windows-latest, tox: py312}
- {name: Mac, python: '3.12', os: macos-latest, tox: py312}
- {name: '3.11', python: '3.11', os: ubuntu-latest, tox: py311}
- {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310}
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
- {name: '3.6', python: '3.6', os: ubuntu-20.04, tox: py36} # ubuntu-latest doesn't support 3.6
- {name: Style, python: '3.10', os: ubuntu-latest, tox: stylecheck}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: update pip
run: |
pip install -U setuptools wheel
python -m pip install -U pip
- run: pip install tox
- run: tox -e ${{ matrix.tox }}

160
.gitignore vendored
View File

@@ -1,4 +1,160 @@
*.egg-info
*.pyc
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "docs/_themes"]
path = docs/_themes
url = https://github.com/mitsuhiko/flask-sphinx-themes.git

View File

@@ -1,2 +1,3 @@
recursive-include flask_debugtoolbar/templates/ *.html
recursive-include flask_debugtoolbar/static/ *
include LICENSE
recursive-include src/flask_debugtoolbar/templates *.html
recursive-include src/flask_debugtoolbar/static *

12
README
View File

@@ -1,12 +0,0 @@
Flask Debug-toolbar
A port of the django-debug toolbar (github.com/robhudson/django-debug-toolbar)
to Flask.
Usage::
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
toolbar = DebugToolbarExtension(app)

43
README.rst Normal file
View File

@@ -0,0 +1,43 @@
Flask Debug-toolbar
===================
This is a port of the excellent `django-debug-toolbar <https://github.com/jazzband/django-debug-toolbar>`_
for Flask applications.
.. image:: https://github.com/pallets-eco/flask-debugtoolbar/actions/workflows/tests.yml/badge.svg
:target: https://github.com/pallets-eco/flask-debugtoolbar/actions
Installation
------------
Installing is simple with pip::
$ pip install flask-debugtoolbar
Usage
-----
Setting up the debug toolbar is simple::
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
# the toolbar is only enabled in debug mode:
app.debug = True
# set a 'SECRET_KEY' to enable the Flask session cookies
app.config['SECRET_KEY'] = '<replace with a secret key>'
toolbar = DebugToolbarExtension(app)
The toolbar will automatically be injected into Jinja templates when debug mode is on.
In production, setting ``app.debug = False`` will disable the toolbar.
See the `documentation`_ for more information.
.. _documentation: https://flask-debugtoolbar.readthedocs.io/

157
docs/Makefile Normal file
View File

@@ -0,0 +1,157 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html: _themes
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-DebugToolbar.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-DebugToolbar.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-DebugToolbar"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-DebugToolbar"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
_themes:
git clone git://github.com/Pylons/pylons_sphinx_theme.git _themes
cd ..; git submodule update --init; cd docs

0
docs/_static/.gitignore vendored Normal file
View File

BIN
docs/_static/example.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
docs/_static/screenshot-config-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
docs/_static/screenshot-logger-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/_static/screenshot-time-panel.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

0
docs/_templates/.gitignore vendored Normal file
View File

1
docs/_themes Submodule

Submodule docs/_themes added at 1cc44686f0

261
docs/conf.py Normal file
View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
#
# Flask-DebugToolbar documentation build configuration file, created by
# sphinx-quickstart on Wed Feb 15 18:08:39 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import datetime
import os
import pkg_resources
import sys
import time
import flask_debugtoolbar
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Flask-DebugToolbar'
copyright = u'2012-{0}'.format(BUILD_DATE.year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = flask_debugtoolbar.__version__
# The short X.Y version.
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
intersphinx_mapping = {
'flasksqlalchemy': ('https://flask-sqlalchemy.palletsprojects.com/', None)
}
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'flask'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
'index_logo': None,
}
# Add any paths that contain custom themes here, relative to this directory.
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Flask-DebugToolbardoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Flask-DebugToolbar.tex', u'Flask-DebugToolbar Documentation',
u'Matt Good', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'flask-debugtoolbar', u'Flask-DebugToolbar Documentation',
[u'Matt Good'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Flask-DebugToolbar', u'Flask-DebugToolbar Documentation',
u'Matt Good', 'Flask-DebugToolbar', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

96
docs/index.rst Normal file
View File

@@ -0,0 +1,96 @@
Flask-DebugToolbar
==================
This extension adds a toolbar overlay to Flask applications containing useful information for debugging.
.. image:: _static/example.gif
Installation
------------
Installing is simple with `pip`_::
$ pip install flask-debugtoolbar
.. _pip: https://pip.pypa.io/
Usage
-----
Setting up the debug toolbar is simple::
from flask import Flask
from flask_debugtoolbar import DebugToolbarExtension
app = Flask(__name__)
# the toolbar is only enabled in debug mode:
app.debug = True
# set a 'SECRET_KEY' to enable the Flask session cookies
app.config['SECRET_KEY'] = '<replace with a secret key>'
toolbar = DebugToolbarExtension(app)
The toolbar will automatically be injected into HTML responses when debug mode
is on. In production, setting ``app.debug = False`` will disable the toolbar.
This extension also supports the Flask app factory pattern by separately
creating the toolbar and later initializing it for an app::
toolbar = DebugToolbarExtension()
# Then later on.
app = create_app('the-config.cfg')
toolbar.init_app(app)
Configuration
-------------
The toolbar support several configuration options:
==================================== ===================================== ==========================
Name Description Default
==================================== ===================================== ==========================
``DEBUG_TB_ENABLED`` Enable the toolbar? ``app.debug``
``DEBUG_TB_HOSTS`` Whitelist of hosts to display toolbar any host
``DEBUG_TB_INTERCEPT_REDIRECTS`` Should intercept redirects? ``True``
``DEBUG_TB_PANELS`` List of module/class names of panels enable all built-in panels
``DEBUG_TB_PROFILER_ENABLED`` Enable the profiler on all requests ``False``, user-enabled
``DEBUG_TB_TEMPLATE_EDITOR_ENABLED`` Enable the template editor ``False``
==================================== ===================================== ==========================
To change one of the config options, set it in the Flask app's config like::
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
Panels
------
.. toctree::
panels
Contributing
------------
Fork us `on GitHub <https://github.com/pallets-eco/flask-debugtoolbar>`_
Thanks
------
This was based on the original `django-debug-toolbar`_. Thanks to `Michael van Tellingen`_ for the original development of this Flask extension, and to all the `individual contributors`_.
.. _django-debug-toolbar: https://github.com/jazzband/django-debug-toolbar
.. _Michael van Tellingen: https://github.com/mvantellingen
.. _individual contributors: https://github.com/pallets-eco/flask-debugtoolbar/graphs/contributors
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

110
docs/panels.rst Normal file
View File

@@ -0,0 +1,110 @@
Built-In Panels
===============
Versions
--------
flask_debugtoolbar.panels.versions.VersionDebugPanel
Shows the installed Flask version. The expanded view displays all installed packages and their versions as detected by ``setuptools``.
Time
----
flask_debugtoolbar.panels.timer.TimerDebugPanel
Shows the time taken to process the current request. The expanded view includes the breakdown of CPU time, by user and system, wall clock time, and context switches.
.. image:: _static/screenshot-time-panel.png
HTTP Headers
------------
flask_debugtoolbar.panels.headers.HeaderDebugPanel
Displays the HTTP headers for the current request.
.. image:: _static/screenshot-headers-panel.png
Request Vars
------------
flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel
Displays details of the Flask request-related variables, including the view function parameters, cookies, session variables, and GET and POST variables.
.. image:: _static/screenshot-request-vars-panel.png
Config
------
flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel
Shows the contents of the Flask application's config dict ``app.config``.
.. image:: _static/screenshot-config-panel.png
Templates
---------
flask_debugtoolbar.panels.template.TemplateDebugPanel
Shows information about the templates rendered for this request, and the value of the template parameters provided.
.. image:: _static/screenshot-template-panel.png
SQLAlchemy
----------
flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel
Shows SQL queries run during the current request.
.. note:: This panel requires using the `Flask-SQLAlchemy`_ extension in order
to record the queries. See the Flask-SQLAlchemy
:ref:`flasksqlalchemy:quickstart` section to configure it.
For additional details on query recording see the
:py:func:`~flask_sqlalchemy.get_debug_queries` documentation.
.. note:: SQL syntax highlighting requires `Pygments`_ to be installed.
.. image:: _static/screenshot-sqlalchemy-panel.png
.. _Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/
.. _Pygments: https://pygments.org/
Logging
-------
flask_debugtoolbar.panels.logger.LoggingPanel
Displays log messages recorded during the current request.
.. image:: _static/screenshot-logger-panel.png
Route List
----------
flask_debugtoolbar.panels.route_list.RouteListDebugPanel
Displays the Flask URL routing rules.
Profiler
--------
flask_debugtoolbar.panels.profiler.ProfilerDebugPanel
Reports profiling data for the current request. Due to the performance overhead, profiling is disabled by default. Click the checkmark to toggle profiling on or off. After enabling the profiler, refresh the page to re-run it with profiling.
.. image:: _static/screenshot-profiler-panel.png

View File

@@ -1,9 +1,7 @@
import sys
sys.path.insert(0, '.')
# Run using: `FLASK_DEBUG=True flask run`
from flask import Flask, render_template, redirect, url_for
from flaskext.script import Manager
from flaskext.sqlalchemy import SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
from flask_debugtoolbar import DebugToolbarExtension
@@ -14,33 +12,35 @@ app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = True
# 'flask_debugtoolbar.panels.logger.LoggingPanel',
# 'flask_debugtoolbar.panels.timer.TimerDebugPanel',
#)
#app.config['DEBUG_TB_HOSTS'] = ('127.0.0.1', '::1' )
app.config['SECRET_KEY'] = 'asd'
app.config['DEBUG'] = True
app.config['SQLALCHEMY_RECORD_QUERIES'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
# This is no longer needed for Flask-SQLAlchemy 3.0+, if you're using 2.X you'll want to define this:
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
toolbar = DebugToolbarExtension(app)
class ExampleModel(db.Model):
__tablename__ = 'examples'
value = db.Column(db.String(100), primary_key=True)
@app.route('/')
def index():
app.logger.info("Hello there")
ExampleModel.query.get(1)
return render_template('index.html')
@app.route('/redirect')
def redirect_example():
response = redirect(url_for('index'))
response.set_cookie('test_cookie', '1')
return response
if __name__ == "__main__":
with app.app_context():
db.create_all()
manager = Manager(app)
manager.run()

View File

@@ -1,165 +0,0 @@
import os
from flask import current_app, request, g
from flask.globals import _request_ctx_stack
from flask import send_from_directory
from jinja2 import Environment, PackageLoader
from werkzeug.exceptions import HTTPException
from werkzeug.urls import url_quote_plus
from flask_debugtoolbar.toolbar import DebugToolbar
from flask import Blueprint
module = Blueprint('debugtoolbar', __name__)
def replace_insensitive(string, target, replacement):
"""Similar to string.replace() but is case insensitive
Code borrowed from: http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
"""
no_case = string.lower()
index = no_case.rfind(target.lower())
if index >= 0:
return string[:index] + replacement + string[index + len(target):]
else: # no results so return the original string
return string
class DebugToolbarExtension(object):
_static_dir = os.path.realpath(
os.path.join(os.path.dirname(__file__), 'static'))
_redirect_codes = [301, 302, 303, 304]
def __init__(self, app):
self.app = app
self.debug_toolbars = {}
if not app.debug:
return
if not app.config.get('SECRET_KEY'):
raise RuntimeError(
"The Flask-DebugToolbar requires the 'SECRET_KEY' config "
"var to be set")
DebugToolbar.load_panels(app)
self.app.before_request(self.process_request)
self.app.after_request(self.process_response)
# Monkey-patch the Flask.dispatch_request method
app.dispatch_request = self.dispatch_request
# Configure jinja for the internal templates and add url rules
# for static data
self.jinja_env = Environment(
autoescape=True,
extensions=['jinja2.ext.i18n'],
loader=PackageLoader(__name__, 'templates'))
self.jinja_env.filters['urlencode'] = url_quote_plus
app.add_url_rule('/_debug_toolbar/static/<path:filename>',
'_debug_toolbar.static', self.send_static_file)
app.register_blueprint(module, url_prefix='/_debug_toolbar/views')
def dispatch_request(self):
"""Modified version of Flask.dispatch_request to call process_view."""
req = _request_ctx_stack.top.request
app = current_app
if req.routing_exception is not None:
app.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return app.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
view_func = app.view_functions[rule.endpoint]
view_func = self.process_view(app, view_func, req.view_args)
return view_func(**req.view_args)
def _show_toolbar(self):
"""Return a boolean to indicate if we need to show the toolbar."""
if request.path.startswith('/_debug_toolbar/'):
return False
return True
def send_static_file(self, filename):
"""Send a static file from the flask-debugtoolbar static directory."""
return send_from_directory(self._static_dir, filename)
def process_request(self):
g.debug_toolbar = self
if not self._show_toolbar():
return
real_request = request._get_current_object()
self.debug_toolbars[real_request] = DebugToolbar(real_request, self.jinja_env)
for panel in self.debug_toolbars[real_request].panels:
panel.process_request(real_request)
def process_view(self, app, view_func, view_kwargs):
""" This method is called just before the flask view is called.
This is done by the dispatch_request method.
"""
real_request = request._get_current_object()
if real_request in self.debug_toolbars:
for panel in self.debug_toolbars[real_request].panels:
new_view = panel.process_view(real_request, view_func, view_kwargs)
if new_view:
view_func = new_view
return view_func
def process_response(self, response):
real_request = request._get_current_object()
if real_request not in self.debug_toolbars:
return response
# Intercept http redirect codes and display an html page with a
# link to the target.
if self.debug_toolbars[real_request].config['DEBUG_TB_INTERCEPT_REDIRECTS']:
if response.status_code in self._redirect_codes:
redirect_to = response.location
redirect_code = response.status_code
if redirect_to:
content = self.render('redirect.html', {
'redirect_to': redirect_to,
'redirect_code': redirect_code
})
response.content_length = len(content)
response.location = None
response.response = [content]
response.status_code = 200
# If the http response code is 200 then we process to add the
# toolbar to the returned html response.
if (response.status_code == 200
and response.headers['content-type'].startswith('text/html')):
for panel in self.debug_toolbars[real_request].panels:
panel.process_response(real_request, response)
if response.is_sequence:
response_html = response.data.decode(response.charset)
toolbar_html = self.debug_toolbars[real_request].render_toolbar()
content = replace_insensitive(
response_html, '</body>', toolbar_html + '</body>')
content = content.encode(response.charset)
response.response = [content]
response.content_length = len(content)
return response
def render(self, template_name, context):
template = self.jinja_env.get_template(template_name)
return template.render(**context)

View File

@@ -1,153 +0,0 @@
import hashlib
try:
from flaskext.sqlalchemy import get_debug_queries, SQLAlchemy
except ImportError:
sqlalchemy_available = False
get_debug_queries = SQLAlchemy = None
else:
sqlalchemy_available = True
from flask import request, current_app, abort, json_available, g
from flask.helpers import json
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
from flask_debugtoolbar.utils import format_fname, format_sql
_ = lambda x: x
class SQLAlchemyDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'SQLAlchemy'
@property
def has_content(self):
if not json_available or not sqlalchemy_available:
return True # will display an error message
return bool(get_debug_queries())
def process_request(self, request):
pass
def process_response(self, request, response):
pass
def nav_title(self):
return _('SQLAlchemy')
def nav_subtitle(self):
if not json_available or not sqlalchemy_available:
return 'Unavailable'
if get_debug_queries:
count = len(get_debug_queries())
return "%d %s" % (count, "query" if count == 1 else "queries")
def title(self):
return _('SQLAlchemy queries')
def url(self):
return ''
def content(self):
if not json_available or not sqlalchemy_available:
msg = ['Missing required libraries:', '<ul>']
if not json_available:
msg.append('<li>simplejson</li>')
if not sqlalchemy_available:
msg.append('<li>Flask-SQLAlchemy</li>')
msg.append('</ul>')
return '\n'.join(msg)
queries = get_debug_queries()
data = []
for query in queries:
is_select = query.statement.strip().lower().startswith('select')
_params = ''
try:
_params = json.dumps(query.parameters)
except TypeError:
pass # object not JSON serializable
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] +
query.statement + _params).hexdigest()
data.append({
'duration': query.duration,
'sql': format_sql(query.statement, query.parameters),
'raw_sql': query.statement,
'hash': hash,
'params': _params,
'is_select': is_select,
'context_long': query.context,
'context': format_fname(query.context)
})
return self.render('panels/sqlalchemy.html', { 'queries': data})
# Panel views
@module.route('/sqlalchemy/sql_select', methods=['GET', 'POST'])
def sql_select():
return ''
statement = request.args['sql']
params = request.args['params']
# Validate hash
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] + statement + params).hexdigest()
if hash != request.args['hash']:
return abort(406)
# Make sure it is a select statement
if not statement.lower().strip().startswith('select'):
return abort(406)
params = json.loads(params)
engine = SQLAlchemy().get_engine(current_app)
result = engine.execute(statement, params)
return g.debug_toolbar.render('panels/sqlalchemy_select.html', {
'result': result.fetchall(),
'headers': result.keys(),
'sql': format_sql(statement, params),
'duration': float(request.args['duration']),
})
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'])
def sql_explain():
statement = request.args['sql']
params = request.args['params']
# Validate hash
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] + statement + params).hexdigest()
if hash != request.args['hash']:
return abort(406)
# Make sure it is a select statement
if not statement.lower().strip().startswith('select'):
return abort(406)
params = json.loads(params)
engine = SQLAlchemy().get_engine(current_app)
if engine.driver == 'pysqlite':
query = 'EXPLAIN QUERY PLAN %s' % statement
else:
query = 'EXPLAIN %s' % statement
result = engine.execute(query, params)
return g.debug_toolbar.render('panels/sqlalchemy_explain.html', {
'result': result.fetchall(),
'headers': result.keys(),
'sql': format_sql(statement, params),
'duration': float(request.args['duration']),
})

View File

@@ -1,44 +0,0 @@
from flask import template_rendered
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class TemplateDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'Template'
has_content = True
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.templates = []
template_rendered.connect(self._store_template_info)
def _store_template_info(self, sender, **kwargs):
self.templates.append(kwargs)
def process_request(self, request):
pass
def process_response(self, request, response):
pass
def nav_title(self):
return _('Templates')
def nav_subtitle(self):
return "%d rendered" % len(self.templates)
def title(self):
return _('Templates')
def url(self):
return ''
def content(self):
return self.render('panels/template.html', {
'templates': self.templates
})

View File

@@ -1,28 +0,0 @@
from flask import __version__ as flask_version
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class VersionDebugPanel(DebugPanel):
"""
Panel that displays the Flask version.
"""
name = 'Version'
has_content = False
def nav_title(self):
return _('Versions')
def nav_subtitle(self):
return 'Flask %s' % flask_version
def url(self):
return ''
def title(self):
return _('Versions')
def content(self):
return None

View File

@@ -1,418 +0,0 @@
/* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */
#flDebug {color:#000;background:#FFF;}
#flDebug, #flDebug div, #flDebug span, #flDebug applet, #flDebug object, #flDebug iframe,
#flDebug h1, #flDebug h2, #flDebug h3, #flDebug h4, #flDebug h5, #flDebug h6, #flDebug p, #flDebug blockquote, #flDebug pre,
#flDebug a, #flDebug abbr, #flDebug acronym, #flDebug address, #flDebug big, #flDebug cite, #flDebug code,
#flDebug del, #flDebug dfn, #flDebug em, #flDebug font, #flDebug img, #flDebug ins, #flDebug kbd, #flDebug q, #flDebug s, #flDebug samp,
#flDebug small, #flDebug strike, #flDebug strong, #flDebug sub, #flDebug sup, #flDebug tt, #flDebug var,
#flDebug b, #flDebug u, #flDebug i, #flDebug center,
#flDebug dl, #flDebug dt, #flDebug dd, #flDebug ol, #flDebug ul, #flDebug li,
#flDebug fieldset, #flDebug form, #flDebug label, #flDebug legend,
#flDebug table, #flDebug caption, #flDebug tbody, #flDebug tfoot, #flDebug thead, #flDebug tr, #flDebug th, #flDebug td {
margin:0;
padding:0;
border:0;
outline:0;
font-size:12px;
line-height:1.5em;
color:#000;
vertical-align:baseline;
background:transparent;
font-family:sans-serif;
text-align:left;
}
#flDebug #flDebugToolbar {
background:#111;
width:200px;
z-index:100000000;
position:fixed;
top:0;
bottom:0;
right:0;
opacity:0.9;
}
#flDebug #flDebugToolbar small {
color:#999;
}
#flDebug #flDebugToolbar ul {
margin:0;
padding:0;
list-style:none;
}
#flDebug #flDebugToolbar li {
border-bottom:1px solid #222;
color:#fff;
display:block;
font-weight:bold;
float:none;
margin:0;
padding:0;
position:relative;
width:auto;
}
#flDebug #flDebugToolbar li>a,
#flDebug #flDebugToolbar li>div.contentless {
font-weight:normal;
font-style:normal;
text-decoration:none;
display:block;
font-size:16px;
padding:10px 10px 5px 25px;
color:#fff;
}
#flDebug #flDebugToolbar li a:hover {
color:#111;
background-color:#ffc;
}
#flDebug #flDebugToolbar li.active {
background-image:url(../img/indicator.png);
background-repeat:no-repeat;
background-position:left center;
background-color:#333;
padding-left:10px;
}
#flDebug #flDebugToolbar li.active a:hover {
color:#b36a60;
background-color:transparent;
}
#flDebug #flDebugToolbar li small {
font-size:12px;
color:#999;
font-style:normal;
text-decoration:none;
font-variant:small-caps;
}
#flDebug #flDebugToolbar li .switch {
font-size: 10px;
position: absolute;
display: block;
color: white;
height: 16px;
width: 16px;
cursor: pointer;
top: 15px;
right: 2px;
}
#flDebug #flDebugToolbar li .switch.active {
background-image: url(../img/tick.png);
}
#flDebug #flDebugToolbar li .switch.inactive {
background-image: url(../img/tick-red.png);
}
#flDebug #flDebugToolbarHandle {
position:fixed;
background:#fff;
border:1px solid #111;
top:30px;
right:0;
z-index:100000000;
opacity:0.75;
}
#flDebug a#flShowToolBarButton {
display:block;
height:75px;
width:30px;
border-right:none;
border-bottom:4px solid #fff;
border-top:4px solid #fff;
border-left:4px solid #fff;
color:#fff;
font-size:10px;
font-weight:bold;
text-decoration:none;
text-align:center;
text-indent:-999999px;
background:#000 url(../img/djdt_vertical.png) no-repeat left center;
opacity:0.5;
}
#flDebug a#flShowToolBarButton:hover {
background-color:#111;
padding-right:6px;
border-top-color:#FFE761;
border-left-color:#FFE761;
border-bottom-color:#FFE761;
opacity:1.0;
}
#flDebug code {
display:block;
font-family:Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", monospace;
white-space:pre;
overflow:auto;
}
#flDebug tr.flDebugOdd {
background-color:#f5f5f5;
}
#flDebug .panelContent {
display:none;
position:fixed;
margin:0;
top:0;
right:200px;
bottom:0;
left:0px;
background-color:#eee;
color:#666;
z-index:100000000;
}
#flDebug .panelContent > div {
border-bottom:1px solid #ddd;
}
#flDebug .flDebugPanelTitle {
position:absolute;
background-color:#ffc;
color:#666;
padding-left:20px;
top:0;
right:0;
left:0;
height:50px;
}
#flDebug .flDebugPanelTitle code {
display:inline;
font-size:inherit;
}
#flDebug .flDebugPanelContent {
position:absolute;
top:50px;
right:0;
bottom:0;
left:0;
height:auto;
padding:0 0 0 20px;
}
#flDebug .flDebugPanelContent .scroll {
height:100%;
overflow:auto;
display:block;
padding:0 10px 0 0;
}
#flDebug h3 {
font-size:24px;
font-weight:normal;
line-height:50px;
}
#flDebug h4 {
font-size:20px;
font-weight:bold;
margin-top:0.8em;
}
#flDebug .panelContent table {
border:1px solid #ccc;
border-collapse:collapse;
width:100%;
background-color:#fff;
display:block;
margin-top:0.8em;
overflow: auto;
}
#flDebug .panelContent tbody td,
#flDebug .panelContent tbody th {
vertical-align:top;
padding:2px 3px;
}
#flDebug .panelContent thead th {
padding:1px 6px 1px 3px;
text-align:left;
font-weight:bold;
font-size:14px;
}
#flDebug .panelContent tbody th {
width:12em;
text-align:right;
color:#666;
padding-right:.5em;
}
#flDebug .flTemplateHideContextDiv {
background-color:#fff;
}
/*
#flDebug .panelContent p a:hover, #flDebug .panelContent dd a:hover {
color:#111;
background-color:#ffc;
}
#flDebug .panelContent p {
padding:0 5px;
}
#flDebug .panelContent p, #flDebug .panelContent table, #flDebug .panelContent ol, #flDebug .panelContent ul, #flDebug .panelContent dl {
margin:5px 0 15px;
background-color:#fff;
}
#flDebug .panelContent table {
clear:both;
border:0;
padding:0;
margin:0;
border-collapse:collapse;
border-spacing:0;
}
#flDebug .panelContent table a {
color:#000;
padding:2px 4px;
}
#flDebug .panelContent table a:hover {
background-color:#ffc;
}
#flDebug .panelContent table th {
background-color:#333;
font-weight:bold;
color:#fff;
padding:3px 7px 3px;
text-align:left;
cursor:pointer;
}
#flDebug .panelContent table td {
padding:5px 10px;
font-size:14px;
background:#fff;
color:#000;
vertical-align:top;
border:0;
}
#flDebug .panelContent table tr.flDebugOdd td {
background:#eee;
}
*/
#flDebug .panelContent .flDebugClose {
text-indent:-9999999px;
display:block;
position:absolute;
top:4px;
right:15px;
height:40px;
width:40px;
background:url(../img/close.png) no-repeat center center;
}
#flDebug .panelContent .flDebugClose:hover {
background-image:url(../img/close_hover.png);
}
#flDebug .panelContent .flDebugClose.flDebugBack {
background-image:url(../img/back.png);
}
#flDebug .panelContent .flDebugClose.flDebugBack:hover {
background-image:url(../img/back_hover.png);
}
#flDebug .panelContent dt, #flDebug .panelContent dd {
display:block;
}
#flDebug .panelContent dt {
margin-top:0.75em;
}
#flDebug .panelContent dd {
margin-left:10px;
}
#flDebug a.toggleTemplate {
padding:4px;
background-color:#bbb;
-moz-border-radius:3px;
-webkit-border-radius:3px;
}
#flDebug a.toggleTemplate:hover {
padding:4px;
background-color:#444;
color:#ffe761;
-moz-border-radius:3px;
-webkit-border-radius:3px;
}
#flDebug a.flTemplateShowContext, #flDebug a.flTemplateShowContext span.toggleArrow {
color:#999;
}
#flDebug a.flTemplateShowContext:hover, #flDebug a.flTemplateShowContext:hover span.toggleArrow {
color:#000;
cursor:pointer;
}
#flDebug .flDebugSqlWrap {
position:relative;
}
#flDebug .flDebugSql {
z-index:100000002;
}
#flDebug .flSQLHideStacktraceDiv tbody th {
text-align: left;
}
#flDebug .flSqlExplain td {
white-space: pre;
}
#flDebug span.flDebugLineChart {
background-color:#777;
height:3px;
position:absolute;
bottom:0;
top:0;
left:0;
display:block;
z-index:1000000001;
}
#flDebug span.flDebugLineChartWarning {
background-color:#900;
}
#flDebug .highlight { color:#000; }
#flDebug .highlight .err { color:#000; } /* Error */
#flDebug .highlight .g { color:#000; } /* Generic */
#flDebug .highlight .k { color:#000; font-weight:bold } /* Keyword */
#flDebug .highlight .o { color:#000; } /* Operator */
#flDebug .highlight .n { color:#000; } /* Name */
#flDebug .highlight .mi { color:#000; font-weight:bold } /* Literal.Number.Integer */
#flDebug .highlight .l { color:#000; } /* Literal */
#flDebug .highlight .x { color:#000; } /* Other */
#flDebug .highlight .p { color:#000; } /* Punctuation */
#flDebug .highlight .m { color:#000; font-weight:bold } /* Literal.Number */
#flDebug .highlight .s { color:#333 } /* Literal.String */
#flDebug .highlight .w { color:#888888 } /* Text.Whitespace */
#flDebug .highlight .il { color:#000; font-weight:bold } /* Literal.Number.Integer.Long */
#flDebug .highlight .na { color:#333 } /* Name.Attribute */
#flDebug .highlight .nt { color:#000; font-weight:bold } /* Name.Tag */
#flDebug .highlight .nv { color:#333 } /* Name.Variable */
#flDebug .highlight .s2 { color:#333 } /* Literal.String.Double */
#flDebug .highlight .cp { color:#333 } /* Comment.Preproc */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

View File

@@ -1,184 +0,0 @@
(function(window, document, version, callback) {
var j, d;
var loaded = false;
if (!(j = window.jQuery) || version > j.fn.jquery || callback(j)) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = DEBUG_TOOLBAR_STATIC_PATH + "js/jquery.js";
script.onload = script.onreadystatechange = function() {
if (!loaded && (!(d = this.readyState) || d == "loaded" || d == "complete")) {
callback((j = window.jQuery).noConflict(1), loaded = true);
j(script).remove();
}
};
document.documentElement.childNodes[0].appendChild(script)
}
})(window, document, "1.3", function($, jquery_loaded) {
$.cookie = function(name, value, options) { if (typeof value != 'undefined') { options = options || {}; if (value === null) { value = ''; options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); } var path = options.path ? '; path=' + (options.path) : ''; var domain = options.domain ? '; domain=' + (options.domain) : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = $.trim(cookies[i]); if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } };
$('head').append('<link rel="stylesheet" href="'+DEBUG_TOOLBAR_STATIC_PATH+'css/toolbar.css?'+ Math.random() +'" type="text/css" />');
var COOKIE_NAME = 'fldt';
var COOKIE_NAME_ACTIVE = COOKIE_NAME +'_active';
var fldt = {
init: function() {
$('#flDebug').show();
var current = null;
$('#flDebugPanelList li a').click(function() {
if (!this.className) {
return false;
}
current = $('#flDebug #' + this.className + '-content');
if (current.is(':visible')) {
$(document).trigger('close.flDebug');
$(this).parent().removeClass('active');
} else {
$('.panelContent').hide(); // Hide any that are already open
current.show();
$('#flDebugToolbar li').removeClass('active');
$(this).parent().addClass('active');
}
return false;
});
$('#flDebugPanelList li .switch').click(function() {
var $panel = $(this).parent();
var $this = $(this);
var dom_id = $panel.attr('id');
// Turn cookie content into an array of active panels
var active_str = $.cookie(COOKIE_NAME_ACTIVE);
var active = (active_str) ? active_str.split(';') : [];
active = $.grep(active, function(n,i) { return n != dom_id; });
if ($this.hasClass('active')) {
$this.removeClass('active');
$this.addClass('inactive');
}
else {
active.push(dom_id);
$this.removeClass('inactive');
$this.addClass('active');
}
if (active.length > 0) {
$.cookie(COOKIE_NAME_ACTIVE, active.join(';'), {
path: '/', expires: 10
});
}
else {
$.cookie(COOKIE_NAME_ACTIVE, null, {
path: '/', expires: -1
});
}
});
$('#flDebug a.flDebugClose').click(function() {
$(document).trigger('close.flDebug');
$('#flDebugToolbar li').removeClass('active');
return false;
});
$('#flDebug a.remoteCall').click(function() {
$('#flDebugWindow').load(this.href, {}, function() {
$('#flDebugWindow a.flDebugBack').click(function() {
$(this).parent().parent().hide();
return false;
});
});
$('#flDebugWindow').show();
return false;
});
$('#flDebugTemplatePanel a.flTemplateShowContext').click(function() {
fldt.toggle_arrow($(this).children('.toggleArrow'))
fldt.toggle_content($(this).parent().next());
return false;
});
$('#flDebugSQLPanel a.flSQLShowStacktrace').click(function() {
fldt.toggle_content($('.flSQLHideStacktraceDiv', $(this).parents('tr')));
return false;
});
$('#flHideToolBarButton').click(function() {
fldt.hide_toolbar(true);
return false;
});
$('#flShowToolBarButton').click(function() {
fldt.show_toolbar();
return false;
});
$(document).bind('close.flDebug', function() {
// If a sub-panel is open, close that
if ($('#flDebugWindow').is(':visible')) {
$('#flDebugWindow').hide();
return;
}
// If a panel is open, close that
if ($('.panelContent').is(':visible')) {
$('.panelContent').hide();
return;
}
// Otherwise, just minimize the toolbar
if ($('#flDebugToolbar').is(':visible')) {
fldt.hide_toolbar(true);
return;
}
});
if ($.cookie(COOKIE_NAME)) {
fldt.hide_toolbar(false);
} else {
fldt.show_toolbar(false);
}
},
toggle_content: function(elem) {
if (elem.is(':visible')) {
elem.hide();
} else {
elem.show();
}
},
close: function() {
$(document).trigger('close.flDebug');
return false;
},
hide_toolbar: function(setCookie) {
// close any sub panels
$('#flDebugWindow').hide();
// close all panels
$('.panelContent').hide();
$('#flDebugToolbar li').removeClass('active');
// finally close toolbar
$('#flDebugToolbar').hide('fast');
$('#flDebugToolbarHandle').show();
// Unbind keydown
$(document).unbind('keydown.flDebug');
if (setCookie) {
$.cookie(COOKIE_NAME, 'hide', {
path: '/',
expires: 10
});
}
},
show_toolbar: function(animate) {
// Set up keybindings
$(document).bind('keydown.flDebug', function(e) {
if (e.keyCode == 27) {
fldt.close();
}
});
$('#flDebugToolbarHandle').hide();
if (animate) {
$('#flDebugToolbar').show('fast');
} else {
$('#flDebugToolbar').show();
}
$.cookie(COOKIE_NAME, null, {
path: '/',
expires: -1
});
},
toggle_arrow: function(elem) {
var uarr = String.fromCharCode(0x25b6);
var darr = String.fromCharCode(0x25bc);
elem.html(elem.html() == uarr ? darr : uarr);
}
};
$(document).ready(function() {
fldt.init();
});
});

View File

@@ -1,54 +0,0 @@
<div id="flDebug" style="display:none;">
<script type="text/javascript">var DEBUG_TOOLBAR_STATIC_PATH = '{{ static_path }}'</script>
<script type="text/javascript" src="{{ static_path }}js/toolbar.js"></script>
<div style="display: none;" id="flDebugToolbar">
<ol id="flDebugPanelList">
{% if panels %}
<li><a id="flHideToolBarButton" href="#" title="Hide Toolbar">Hide &raquo;</a></li>
{% else %}
<li id="flDebugButton">DEBUG</li>
{% endif %}
{% for panel in panels %}
<li id="{{ panel.dom_id() }}">
{% if panel.has_content %}
<a href="{{ panel.url()|default("#") }}" title="{{ panel.title() }}" class="{{ panel.dom_id() }}">
{% else %}
<div class="contentless">
{% endif %}
{{ panel.nav_title() }}
{% if panel.nav_subtitle() %}<br /><small>{{ panel.nav_subtitle() }}</small>{% endif %}
{% if panel.has_content %}
</a>
{% else %}
</div>
{% endif %}
{% if panel.user_activate %}
<span class="switch {{ 'active' if panel.is_active else 'inactive' }}" title="Enable or disable the panel"></span>
{% endif %}
</li>
{% endfor %}
</ol>
</div>
<div style="display:none;" id="flDebugToolbarHandle">
<a title="Show Toolbar" id="flShowToolBarButton" href="#">&laquo;</a>
</div>
{% for panel in panels %}
{% if panel.has_content %}
<div id="{{ panel.dom_id() }}-content" class="panelContent">
<div class="flDebugPanelTitle">
<a href="" class="flDebugClose">Close</a>
<h3>{{ panel.title()|safe }}</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
{{ panel.content()|safe }}
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div id="flDebugWindow" class="panelContent"></div>
</div>

View File

@@ -1,16 +0,0 @@
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in headers.iteritems() %}
<tr class="{{ loop.cycle('djDebugOdd' 'djDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,26 +0,0 @@
{% if records %}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr class="{{ loop.cycle('fjDebugOdd' 'fjDebugEven') }}">
<td>{{ record.level }}</td>
<td>{{ record.time }}</td>
<td>{{ record.message }}</td>
<td title="{{ record.file_long }}:{{ record.line }}">{{ record.file }}:{{ record.line }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No messages logged.</p>
{% endif %}

View File

@@ -1,25 +0,0 @@
<table>
<thead>
<tr>
<th>Calls</th>
<th>Total Time (ms)</th>
<th>Per Call (ms)</th>
<th>Cumulative Time (ms)</th>
<th>Per Call (ms)</th>
<th>Function</th>
</tr>
</thead>
<tbody>
{% for row in function_calls %}
<tr class="{{ loop.cycle('flDebugOdd' 'flDebugEven') }}">
<td>{{ row.ncalls }}</td>
<td>{{ row.tottime }}</td>
<td>{{ '%.4f'|format(row.percall) }}</td>
<td>{{ row.cumtime }}</td>
<td>{{ '%.4f'|format(row.percall_cum) }}</td>
<td title="{{ row.filename_long }}">{{ row.filename|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,123 +0,0 @@
<h4>View information</h4>
<table>
<thead>
<tr>
<th>View Function</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ view_func }}</td>
<td>{{ view_args|default("None") }}</td>
<td>
{% if view_kwargs.items() %}
{% for k, v in view_kwargs.items() %}
{{ k }}={{ v }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% else %}
None
{% endif %}
</td>
</tr>
</tbody>
</table>
<h4>COOKIES Variables</h4>
{% if cookies %}
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in cookies %}
<tr class="{{ loop.cycle('flDebugOdd' 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No COOKIE data</p>
{% endif %}
<h4>SESSION Variables</h4>
{% if session %}
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in session %}
<tr class="{{ loop.cycle('flDebugOdd' 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No SESSION data</p>
{% endif %}
<h4>GET Variables</h4>
{% if get %}
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in get %}
<tr class="{{ loop.cycle('flDebugOdd' 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|join(", ")|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No GET data</p>
{% endif %}
<h4>POST Variables</h4>
{% if post %}
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in post %}
<tr class="{{ loop.cycle('row1' 'row2') }}">
<td>{{ key|escape }}</td>
<td>{{ value|join(", ")|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No POST data</p>
{% endif %}

View File

@@ -1,68 +0,0 @@
<table>
<thead>
<tr>
<th>&nbsp;(ms)</th>
<th>Action</th>
<th>Context</th>
<th>Query</th>
</tr>
</thead>
<tbody>
{% for query in queries %}
<tr class="{{ loop.cycle('flDebugOdd' 'flDebugEven') }}">
<td>{{ '%.4f'|format(query.duration) }}</td>
<td>
{% if query.params %}
{% if query.is_select %}
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_select?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">SELECT</a><br />
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">EXPLAIN</a><br />
{% if is_mysql %}
<a class="remoteCall" href="/__debug__/sql_profile/?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">PROFILE</a><br />
{% endif %}
{% endif %}
{% endif %}
</td>
<td title="{{ query.context_long }}">
{{ query.context }}
</td>
<td class="syntax">
<div class="djDebugSqlWrap">
<div class="flDebugSql">{{ query.sql|safe }}</div>
{#
{% if query.stacktrace %}
<div class="djSQLHideStacktraceDiv" style="display:none;">
<table>
<tr>
<th>{% trans "Line" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "File" %}</th>
</tr>
{% for file, line, method in query.stacktrace %}
<tr>
<td>{{ line }}</td>
<td><code>{{ method|escape }}</code></td>
<td><code>{{ file|escape }}</code></td>
</tr>
{% endfor %}
</table>
{% if query.template_info %}
<table>
{% for line in query.template_info.context %}
<tr>
<td>{{ line.num }}</td>
<td><code style="font-family: monospace;{% if line.highlight %}background-color: lightgrey{% endif %}">{{ line.content }}</code></td>
</tr>
{% endfor %}
</table>
<p><strong>{{ query.template_info.name|default:"(unknown)" }}</strong></p>
{% endif %}
</div>
{% endif %}
<span class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}" style="width:{{ query.width_ratio }}%; left:{{ query.start_offset }}%;"></span>
#}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,33 +0,0 @@
<div class="flDebugPanelTitle">
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
<table class="djSqlExplain">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('fjDebugOdd' 'fjDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@@ -1,37 +0,0 @@
<div class="flDebugPanelTitle">
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
{% if result %}
<table class="flSqlSelect">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('fjDebugOdd' 'fjDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>Empty set</p>
{% endif %}
</div>
</div>

View File

@@ -1,22 +0,0 @@
{% if templates %}
<table>
<thead>
<tr>
<th>Filename</th>
<th>Context vars</th>
</tr>
</thead>
<tbody>
{% for template in templates %}
<tr class="{{ loop.cycle('fjDebugOdd' 'fjDebugEven') }}">
<td>{{ template.template.name }}</td>
<td>{{ template.context.keys()|join(', ') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No templates rendered.</p>
{% endif %}

View File

@@ -1,21 +0,0 @@
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Resource</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in rows %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,15 +0,0 @@
<html>
<head>
<title>Redirect intercepted</title>
</head>
<body>
<h1>Redirect ({{ redirect_code }})</h1>
<p>Location: <a href="{{ redirect_to }}">{{ redirect_to }}</a></p>
<p class="notice">
The Flask Debug Toolbar has intercepted a redirect to the above URL for
debug viewing purposes. You can click the above link to continue with the
redirect as normal. If you'd like to disable this feature, you can set the
config variable <code>DEBUG_TB_INTERCEPT_REDIRECTS</code> to <code>False</code>.
</p>
</body>
</html>

View File

@@ -1,73 +0,0 @@
from flask import url_for, current_app
class DebugToolbar(object):
# default config settings
config = {
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
'DEBUG_TB_PANELS': (
'flask_debugtoolbar.panels.versions.VersionDebugPanel',
'flask_debugtoolbar.panels.timer.TimerDebugPanel',
'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
'flask_debugtoolbar.panels.template.TemplateDebugPanel',
'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
'flask_debugtoolbar.panels.logger.LoggingPanel',
'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
)
}
panel_classes = []
def __init__(self, request, jinja_env):
self.jinja_env = jinja_env
self.request = request
self.panels = []
self.template_context = {
'static_path': url_for('_debug_toolbar.static', filename='')
}
self.create_panels()
@classmethod
def load_panels(cls, app):
cls.config.update(app.config)
for panel_path in cls.config['DEBUG_TB_PANELS']:
dot = panel_path.rindex('.')
panel_module, panel_classname = panel_path[:dot], panel_path[dot+1:]
try:
mod = __import__(panel_module, {}, {}, [''])
except ImportError, e:
app.logger.warning('Disabled %s due to ImportError: %s', panel_classname, e)
continue
panel_class = getattr(mod, panel_classname)
cls.panel_classes.append(panel_class)
def create_panels(self):
"""
Populate debug panels
"""
activated = self.request.cookies.get('fldt_active', '').split(';')
for panel_class in self.panel_classes:
panel_instance = panel_class(
context=self.template_context,
jinja_env=self.jinja_env)
if panel_instance.dom_id() in activated:
panel_instance.is_active = True
self.panels.append(panel_instance)
def render_toolbar(self):
context = self.template_context.copy()
context.update({'panels': self.panels})
template = self.jinja_env.get_template('base.html')
return template.render(**context)

View File

@@ -1,56 +0,0 @@
import os.path
import sys
try:
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import SqlLexer
from pygments.styles import get_style_by_name
PYGMENT_STYLE = get_style_by_name('colorful')
HAVE_PYGMENTS = True
except ImportError:
HAVE_PYGMENTS = False
from flask import current_app
def format_fname(value):
# If the value is not an absolute path, the it is a builtin or
# a relative file (thus a project file).
if not os.path.isabs(value):
if value.startswith(('{', '<')):
return value
if value.startswith('.' + os.path.sep):
return value
return '.' + os.path.sep + value
# If the file is absolute and within the project root handle it as
# a project file
if value.startswith(current_app.root_path):
return "." + value[len(current_app.root_path):]
# Loop through sys.path to find the longest match and return
# the relative path from there.
paths = sys.path
prefix = None
prefix_len = 0
for path in sys.path:
new_prefix = os.path.commonprefix([path, value])
if len(new_prefix) > prefix_len:
prefix = new_prefix
prefix_len = len(prefix)
if not prefix.endswith(os.path.sep):
prefix_len -= 1
path = value[prefix_len:]
return '<%s>' % path
def format_sql(query, args):
if not HAVE_PYGMENTS:
return query
return highlight(
query,
SqlLexer(encoding='utf-8'),
HtmlFormatter(encoding='utf-8', noclasses=True, style=PYGMENT_STYLE))

6
pyproject.toml Normal file
View File

@@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

36
setup.cfg Normal file
View File

@@ -0,0 +1,36 @@
[metadata]
name = Flask-DebugToolbar
version = 0.14.1
author = Michael van Tellingen
author_email = michaelvantellingen@gmail.com
maintainer = Matt Good
maintainer_email = matt@matt-good.net
description = A toolbar overlay for debugging Flask applications.
long_description = file: README.rst
long_description_content_type = text/x-rst
keywords = flask, debug, toolbar
url = https://github.com/pallets-eco/flask-debugtoolbar
project_urls =
Changelog = https://github.com/pallets-eco/flask-debugtoolbar/releases
Documentation = https://flask-debugtoolbar.readthedocs.io/
classifiers =
Development Status :: 4 - Beta
Environment :: Web Environment
Framework :: Flask
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Topic :: Internet :: WWW/HTTP :: Dynamic Content
Topic :: Software Development :: Libraries :: Python Modules
[options]
package_dir =
= src
packages = find:
include_package_data = True
python_requires = >=2.7
[options.packages.find]
where = src

View File

@@ -1,36 +1,14 @@
from setuptools import setup, find_packages
from setuptools import setup
# Metadata goes in setup.cfg. These are here for GitHub's dependency graph.
setup(
name='Flask-DebugToolbar',
version='0.6',
url='http://github.com/mgood/flask-debugtoolbar',
license='BSD',
author='Michael van Tellingen',
author_email='michaelvantellingen@gmail.com',
maintainer='Matt Good',
maintainer_email='matt@matt-good.net',
description='A port of the Django debug toolbar to Flask',
long_description=__doc__,
zip_safe=False,
platforms='any',
include_package_data=True,
packages=['flask_debugtoolbar',
'flask_debugtoolbar.panels'
],
name="Flask-DebugToolbar",
install_requires=[
'setuptools',
'Flask>=0.8',
'Blinker',
'itsdangerous',
'werkzeug',
'MarkupSafe',
'packaging',
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules'
]
)

View File

@@ -0,0 +1,274 @@
import os
import urllib.parse
import warnings
import flask
from packaging import version as version_builder
from flask import Blueprint, current_app, request, g, send_from_directory, url_for
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
from flask.globals import request_ctx
else:
from flask.globals import _request_ctx_stack
from jinja2 import __version__ as __jinja_version__
from jinja2 import Environment, PackageLoader
from flask_debugtoolbar.compat import iteritems
from flask_debugtoolbar.toolbar import DebugToolbar
from flask_debugtoolbar.utils import decode_text, gzip_compress, gzip_decompress
try:
# Python 3.8+
from importlib.metadata import version
__version__ = version("Flask-DebugToolbar")
except ImportError:
import pkg_resources
__version__ = pkg_resources.get_distribution("Flask-DebugToolbar").version
module = Blueprint('debugtoolbar', __name__)
def replace_insensitive(string, target, replacement):
"""Similar to string.replace() but is case insensitive
Code borrowed from:
http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
"""
no_case = string.lower()
index = no_case.rfind(target.lower())
if index >= 0:
return string[:index] + replacement + string[index + len(target):]
else: # no results so return the original string
return string
def _printable(value):
try:
return decode_text(repr(value))
except Exception as e:
return '<repr(%s) raised %s: %s>' % (
object.__repr__(value), type(e).__name__, e)
class DebugToolbarExtension(object):
_static_dir = os.path.realpath(
os.path.join(os.path.dirname(__file__), 'static'))
_toolbar_codes = [200, 201, 400, 401, 403, 404, 405, 500, 501, 502, 503, 504]
_redirect_codes = [301, 302, 303, 304]
def __init__(self, app=None):
self.app = app
self.debug_toolbars = {}
jinja_extensions = ['jinja2.ext.i18n']
if __jinja_version__[0] == '2':
jinja_extensions.append('jinja2.ext.with_')
# Configure jinja for the internal templates and add url rules
# for static data
self.jinja_env = Environment(
autoescape=True,
extensions=jinja_extensions,
loader=PackageLoader(__name__, 'templates'))
self.jinja_env.filters['urlencode'] = urllib.parse.quote_plus
self.jinja_env.filters['printable'] = _printable
self.jinja_env.globals['url_for'] = url_for
if app is not None:
self.init_app(app)
def init_app(self, app):
for k, v in iteritems(self._default_config(app)):
app.config.setdefault(k, v)
if not app.config['DEBUG_TB_ENABLED']:
return
if not app.config.get('SECRET_KEY'):
raise RuntimeError(
"The Flask-DebugToolbar requires the 'SECRET_KEY' config "
"var to be set")
DebugToolbar.load_panels(app)
app.before_request(self.process_request)
app.after_request(self.process_response)
app.teardown_request(self.teardown_request)
# Monkey-patch the Flask.dispatch_request method
app.dispatch_request = self.dispatch_request
app.add_url_rule('/_debug_toolbar/static/<path:filename>',
'_debug_toolbar.static', self.send_static_file)
app.register_blueprint(module, url_prefix='/_debug_toolbar/views')
def _default_config(self, app):
return {
'DEBUG_TB_ENABLED': app.debug,
'DEBUG_TB_HOSTS': (),
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
'DEBUG_TB_PANELS': (
'flask_debugtoolbar.panels.versions.VersionDebugPanel',
'flask_debugtoolbar.panels.timer.TimerDebugPanel',
'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
'flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel',
'flask_debugtoolbar.panels.template.TemplateDebugPanel',
'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
'flask_debugtoolbar.panels.logger.LoggingPanel',
'flask_debugtoolbar.panels.route_list.RouteListDebugPanel',
'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
'flask_debugtoolbar.panels.g.GDebugPanel',
),
'SQLALCHEMY_RECORD_QUERIES': app.debug,
}
def dispatch_request(self):
"""Modified version of Flask.dispatch_request to call process_view."""
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
req = request_ctx.request
else:
req = _request_ctx_stack.top.request
app = current_app
if req.routing_exception is not None:
app.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return app.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
view_func = app.view_functions[rule.endpoint]
view_func = self.process_view(app, view_func, req.view_args)
return view_func(**req.view_args)
def _show_toolbar(self):
"""Return a boolean to indicate if we need to show the toolbar."""
if request.blueprint == 'debugtoolbar':
return False
hosts = current_app.config['DEBUG_TB_HOSTS']
if hosts and request.remote_addr not in hosts:
return False
return True
def send_static_file(self, filename):
"""Send a static file from the flask-debugtoolbar static directory."""
return send_from_directory(self._static_dir, filename)
def process_request(self):
g.debug_toolbar = self
if not self._show_toolbar():
return
real_request = request._get_current_object()
self.debug_toolbars[real_request] = (
DebugToolbar(real_request, self.jinja_env))
for panel in self.debug_toolbars[real_request].panels:
panel.process_request(real_request)
def process_view(self, app, view_func, view_kwargs):
""" This method is called just before the flask view is called.
This is done by the dispatch_request method.
"""
real_request = request._get_current_object()
try:
toolbar = self.debug_toolbars[real_request]
except KeyError:
return view_func
for panel in toolbar.panels:
new_view = panel.process_view(real_request, view_func, view_kwargs)
if new_view:
view_func = new_view
return view_func
def process_response(self, response):
real_request = request._get_current_object()
if real_request not in self.debug_toolbars:
return response
# Intercept http redirect codes and display an html page with a
# link to the target.
if current_app.config['DEBUG_TB_INTERCEPT_REDIRECTS']:
if response.status_code in self._redirect_codes:
redirect_to = response.location
redirect_code = response.status_code
if redirect_to:
content = self.render('redirect.html', {
'redirect_to': redirect_to,
'redirect_code': redirect_code
})
response.content_length = len(content)
response.location = None
response.response = [content]
response.status_code = 200
# If the http response code is an allowed code then we process to add the
# toolbar to the returned html response.
if not (response.status_code in self._toolbar_codes and
response.is_sequence and
response.headers['content-type'].startswith('text/html')):
return response
content_encoding = response.headers.get('Content-Encoding')
if content_encoding and 'gzip' in content_encoding:
response_html = gzip_decompress(response.data).decode()
else:
response_html = response.get_data(as_text=True)
no_case = response_html.lower()
body_end = no_case.rfind('</body>')
if body_end >= 0:
before = response_html[:body_end]
after = response_html[body_end:]
elif no_case.startswith('<!doctype html>'):
before = response_html
after = ''
else:
warnings.warn('Could not insert debug toolbar.'
' </body> tag not found in response.')
return response
toolbar = self.debug_toolbars[real_request]
for panel in toolbar.panels:
panel.process_response(real_request, response)
toolbar_html = toolbar.render_toolbar()
content = ''.join((before, toolbar_html, after))
content = content.encode('utf-8')
if content_encoding and 'gzip' in content_encoding:
content = gzip_compress(content)
response.response = [content]
response.content_length = len(content)
return response
def teardown_request(self, exc):
self.debug_toolbars.pop(request._get_current_object(), None)
def render(self, template_name, context):
template = self.jinja_env.get_template(template_name)
return template.render(**context)

View File

@@ -0,0 +1,9 @@
import sys
PY2 = sys.version_info[0] == 2
if PY2:
iteritems = lambda d: d.iteritems()
else:
iteritems = lambda d: iter(d.items())

View File

@@ -1,16 +1,18 @@
"""Base DebugPanel class"""
class DebugPanel(object):
"""
Base class for debug panels.
"""
# name = Base
has_content = False # If content returns something, set to true in subclass
# If content returns something, set to true in subclass
has_content = False
# If the client is able to activate/de-activate the panel
user_enable = False
# We'll maintain a local context instance so we can expose our template
# context variables to panels which need them:
context = {}
@@ -57,5 +59,3 @@ class DebugPanel(object):
def process_response(self, request, response):
pass

View File

@@ -0,0 +1,29 @@
from flask import current_app
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class ConfigVarsDebugPanel(DebugPanel):
"""
A panel to display all variables from Flask configuration
"""
name = 'ConfigVars'
has_content = True
def nav_title(self):
return _('Config')
def title(self):
return _('Config')
def url(self):
return ''
def content(self):
context = self.context.copy()
context.update({
'config': current_app.config,
})
return self.render('panels/config_vars.html', context)

View File

@@ -0,0 +1,28 @@
from flask import g
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class GDebugPanel(DebugPanel):
"""
A panel to display flask.g content.
"""
name = 'g'
has_content = True
def nav_title(self):
return _('flask.g')
def title(self):
return _('flask.g content')
def url(self):
return ''
def content(self):
context = self.context.copy()
context.update({
'g_content': g.__dict__
})
return self.render('panels/g.html', context)

View File

@@ -12,31 +12,32 @@ from flask_debugtoolbar.utils import format_fname
_ = lambda x: x
class ThreadTrackingHandler(logging.Handler):
def __init__(self):
if threading is None:
raise NotImplementedError("threading module is not available, \
the logging panel cannot be used without it")
logging.Handler.__init__(self)
self.records = {} # a dictionary that maps threads to log records
self.records = {} # a dictionary that maps threads to log records
def emit(self, record):
self.get_records().append(record)
def get_records(self, thread=None):
"""
Returns a list of records for the provided thread, of if none is provided,
returns a list for the current thread.
Returns a list of records for the provided thread, of if none is
provided, returns a list for the current thread.
"""
if thread is None:
thread = threading.currentThread()
thread = threading.current_thread()
if thread not in self.records:
self.records[thread] = []
return self.records[thread]
def clear_records(self, thread=None):
if thread is None:
thread = threading.currentThread()
thread = threading.current_thread()
if thread in self.records:
del self.records[thread]
@@ -46,18 +47,27 @@ _init_lock = threading.Lock()
def _init_once():
# Initialize the logging handler once, but after werkzeug has set up its
# default logger. Otherwise, if this sets up the logging first, werkzeug
# will not create a default logger, so the development server's output will
# not get printed.
global handler
if handler is not None:
return
with _init_lock:
global handler
if handler is not None:
return
handler = ThreadTrackingHandler()
logging.root.addHandler(handler)
return
with _init_lock:
if handler is not None:
return
# Call werkzeug's internal logging to make sure it gets configured
# before we add our handler. Otherwise werkzeug will see our handler
# and not configure console logging for the request log.
# Werkzeug's default log level is INFO so this message probably won't
# be seen.
try:
from werkzeug._internal import _log
except ImportError:
pass
else:
_log('debug', 'Initializing Flask-DebugToolbar log handler')
handler = ThreadTrackingHandler()
logging.root.addHandler(handler)
class LoggingPanel(DebugPanel):
@@ -78,7 +88,8 @@ class LoggingPanel(DebugPanel):
def nav_subtitle(self):
# FIXME l10n: use ngettext
return "%s message%s" % (len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's')
num_records = len(handler.get_records())
return '%s message%s' % (num_records, '' if num_records == 1 else 's')
def title(self):
return _('Log Messages')
@@ -102,5 +113,3 @@ class LoggingPanel(DebugPanel):
context.update({'records': records})
return self.render('panels/logger.html', context)

View File

@@ -1,10 +1,8 @@
import sys
try:
import cProfile as profile
except ImportError:
import profile
import functools
import os.path
import pstats
from flask import current_app
@@ -20,6 +18,11 @@ class ProfilerDebugPanel(DebugPanel):
user_activate = True
def __init__(self, jinja_env, context={}):
DebugPanel.__init__(self, jinja_env, context=context)
if current_app.config.get('DEBUG_TB_PROFILER_ENABLED'):
self.is_active = True
def has_content(self):
return bool(self.profiler)
@@ -32,7 +35,9 @@ class ProfilerDebugPanel(DebugPanel):
def process_view(self, request, view_func, view_kwargs):
if self.is_active:
return functools.partial(self.profiler.runcall, view_func)
func = functools.partial(self.profiler.runcall, view_func)
functools.update_wrapper(func, view_func)
return func
def process_response(self, request, response):
if not self.is_active:
@@ -68,7 +73,7 @@ class ProfilerDebugPanel(DebugPanel):
# Cumulative time
current['cumtime'] = info[3] * 1000
# Quotient of the cumulative time divded by the number of
# Quotient of the cumulative time divided by the number of
# primitive calls.
if info[0]:
current['percall_cum'] = info[3] * 1000 / info[0]
@@ -89,7 +94,7 @@ class ProfilerDebugPanel(DebugPanel):
def title(self):
if not self.is_active:
return "Profiler not active"
return 'View: %.2fms' % (float(self.stats.total_tt)*1000,)
return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,)
def nav_title(self):
return 'Profiler'
@@ -97,7 +102,7 @@ class ProfilerDebugPanel(DebugPanel):
def nav_subtitle(self):
if not self.is_active:
return "in-active"
return 'View: %.2fms' % (float(self.stats.total_tt)*1000,)
return 'View: %.2fms' % (float(self.stats.total_tt) * 1000,)
def url(self):
return ''
@@ -111,6 +116,3 @@ class ProfilerDebugPanel(DebugPanel):
'function_calls': self.function_calls,
}
return self.render('panels/profiler.html', context)

View File

@@ -4,6 +4,7 @@ from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class RequestVarsDebugPanel(DebugPanel):
"""
A panel to display request variables (POST/GET, session, cookies).
@@ -34,14 +35,15 @@ class RequestVarsDebugPanel(DebugPanel):
def content(self):
context = self.context.copy()
context.update({
'get': [(k, self.request.args.getlist(k)) for k in self.request.args],
'post': [(k, self.request.form.getlist(k)) for k in self.request.form],
'cookies': [(k, self.request.cookies.get(k)) for k in self.request.cookies],
'view_func': '%s.%s' % (self.view_func.__module__, self.view_func.__name__) if self.view_func else '[unknown]',
'get': self.request.args.lists(),
'post': self.request.form.lists(),
'cookies': self.request.cookies.items(),
'view_func': ('%s.%s' % (self.view_func.__module__,
self.view_func.__name__)
if self.view_func else '[unknown]'),
'view_args': self.view_args,
'view_kwargs': self.view_kwargs or {},
'session': self.session.items(),
})
return self.render('panels/request_vars.html', context)

View File

@@ -0,0 +1,38 @@
from flask_debugtoolbar.panels import DebugPanel
from flask import current_app
_ = lambda x: x
class RouteListDebugPanel(DebugPanel):
"""
Panel that displays the URL routing rules.
"""
name = 'RouteList'
has_content = True
routes = []
def nav_title(self):
return _('Route List')
def title(self):
return _('Route List')
def url(self):
return ''
def nav_subtitle(self):
count = len(self.routes)
return '%s %s' % (count, 'route' if count == 1 else 'routes')
def process_request(self, request):
self.routes = [
rule
for rule in current_app.url_map.iter_rules()
if not rule.rule.startswith('/_debug_toolbar')
]
def content(self):
return self.render('panels/route_list.html', {
'routes': self.routes,
})

View File

@@ -0,0 +1,156 @@
try:
from flask_sqlalchemy import SQLAlchemy
except ImportError:
sqlalchemy_available = False
get_recorded_queries = SQLAlchemy = None
else:
try:
from flask_sqlalchemy.record_queries import get_recorded_queries
except ImportError:
# For flask_sqlalchemy < 3.0.0
from flask_sqlalchemy import get_debug_queries as get_recorded_queries
location_property = 'context'
else:
location_property = 'location'
sqlalchemy_available = True
from flask import request, current_app, abort, g
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
from flask_debugtoolbar.utils import format_fname, format_sql
import itsdangerous
_ = lambda x: x
def query_signer():
return itsdangerous.URLSafeSerializer(current_app.config['SECRET_KEY'],
salt='fdt-sql-query')
def is_select(statement):
prefix = b'select' if isinstance(statement, bytes) else 'select'
return statement.lower().strip().startswith(prefix)
def dump_query(statement, params):
if not params or not is_select(statement):
return None
try:
return query_signer().dumps([statement, params])
except TypeError:
return None
def load_query(data):
try:
statement, params = query_signer().loads(request.args['query'])
except (itsdangerous.BadSignature, TypeError):
abort(406)
# Make sure it is a select statement
if not is_select(statement):
abort(406)
return statement, params
def extension_used():
return 'sqlalchemy' in current_app.extensions
def recording_enabled():
return (current_app.debug or current_app.config.get('SQLALCHEMY_RECORD_QUERIES'))
def is_available():
return sqlalchemy_available and extension_used() and recording_enabled()
def get_queries():
if get_recorded_queries:
return get_recorded_queries()
else:
return []
class SQLAlchemyDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'SQLAlchemy'
@property
def has_content(self):
return bool(get_queries()) or not is_available()
def process_request(self, request):
pass
def process_response(self, request, response):
pass
def nav_title(self):
return _('SQLAlchemy')
def nav_subtitle(self):
count = len(get_queries())
if not count and not is_available():
return 'Unavailable'
return '%d %s' % (count, 'query' if count == 1 else 'queries')
def title(self):
return _('SQLAlchemy queries')
def url(self):
return ''
def content(self):
queries = get_queries()
if not queries and not is_available():
return self.render('panels/sqlalchemy_error.html', {
'sqlalchemy_available': sqlalchemy_available,
'extension_used': extension_used(),
'recording_enabled': recording_enabled(),
})
data = []
for query in queries:
data.append({
'duration': query.duration,
'sql': format_sql(query.statement, query.parameters),
'signed_query': dump_query(query.statement, query.parameters),
'location_long': getattr(query, location_property),
'location': format_fname(getattr(query, location_property))
})
return self.render('panels/sqlalchemy.html', {'queries': data})
# Panel views
@module.route('/sqlalchemy/sql_select', methods=['GET', 'POST'])
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'],
defaults=dict(explain=True))
def sql_select(explain=False):
statement, params = load_query(request.args['query'])
engine = SQLAlchemy().get_engine(current_app)
if explain:
if engine.driver == 'pysqlite':
statement = 'EXPLAIN QUERY PLAN\n%s' % statement
else:
statement = 'EXPLAIN\n%s' % statement
result = engine.execute(statement, params)
return g.debug_toolbar.render('panels/sqlalchemy_select.html', {
'result': result.fetchall(),
'headers': result.keys(),
'sql': format_sql(statement, params),
'duration': float(request.args['duration']),
})

View File

@@ -0,0 +1,136 @@
import collections
import json
import sys
import uuid
from flask import (
template_rendered, request, g,
Response, current_app, abort, url_for
)
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class TemplateDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'Template'
has_content = True
# save the context for the 5 most recent requests
template_cache = collections.deque(maxlen=5)
@classmethod
def get_cache_for_key(self, key):
for cache_key, value in self.template_cache:
if key == cache_key:
return value
raise KeyError(key)
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.key = str(uuid.uuid4())
self.templates = []
template_rendered.connect(self._store_template_info)
def _store_template_info(self, sender, **kwargs):
# only record in the cache if the editor is enabled and there is
# actually a template for this request
if not self.templates and is_editor_enabled():
self.template_cache.append((self.key, self.templates))
self.templates.append(kwargs)
def process_request(self, request):
pass
def process_response(self, request, response):
pass
def nav_title(self):
return _('Templates')
def nav_subtitle(self):
return "%d rendered" % len(self.templates)
def title(self):
return _('Templates')
def url(self):
return ''
def content(self):
return self.render('panels/template.html', {
'key': self.key,
'templates': self.templates,
'editable': is_editor_enabled(),
})
def is_editor_enabled():
return current_app.config.get('DEBUG_TB_TEMPLATE_EDITOR_ENABLED')
def require_enabled():
if not is_editor_enabled():
abort(403)
def _get_source(template):
with open(template.filename, 'rb') as fp:
source = fp.read()
return source.decode(_template_encoding())
def _template_encoding():
return getattr(current_app.jinja_loader, 'encoding', 'utf-8')
@module.route('/template/<key>')
def template_editor(key):
require_enabled()
# TODO set up special loader that caches templates it loads
# and can override template contents
templates = [t['template'] for t in
TemplateDebugPanel.get_cache_for_key(key)]
return g.debug_toolbar.render('panels/template_editor.html', {
'static_path': url_for('_debug_toolbar.static', filename=''),
'request': request,
'templates': [
{'name': t.name, 'source': _get_source(t)}
for t in templates
]
})
@module.route('/template/<key>/save', methods=['POST'])
def save_template(key):
require_enabled()
template = TemplateDebugPanel.get_cache_for_key(key)[0]['template']
content = request.form['content'].encode(_template_encoding())
with open(template.filename, 'wb') as fp:
fp.write(content)
return 'ok'
@module.route('/template/<key>', methods=['POST'])
def template_preview(key):
require_enabled()
context = TemplateDebugPanel.get_cache_for_key(key)[0]['context']
content = request.form['content']
env = current_app.jinja_env.overlay(autoescape=True)
try:
template = env.from_string(content)
return template.render(context)
except Exception as e:
tb = sys.exc_info()[2]
try:
while tb.tb_next:
tb = tb.tb_next
msg = {'lineno': tb.tb_lineno, 'error': str(e)}
return Response(json.dumps(msg), status=400,
mimetype='application/json')
finally:
del tb

View File

@@ -1,18 +1,19 @@
try:
import resource
except ImportError:
pass # Will fail on Win32 systems
pass # Will fail on Win32 systems
import time
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class TimerDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'Timer'
try: # if resource module not available, don't show content panel
try: # if resource module not available, don't show content panel
resource
except NameError:
has_content = False
@@ -36,13 +37,14 @@ class TimerDebugPanel(DebugPanel):
def nav_subtitle(self):
# TODO l10n
if self.has_resource:
utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime
stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime
return 'CPU: %0.2fms (%0.2fms)' % ((utime + stime) * 1000.0, self.total_time)
else:
if not self.has_resource:
return 'TOTAL: %0.2fms' % (self.total_time)
utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime
stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime
return 'CPU: %0.2fms (%0.2fms)' % (
(utime + stime) * 1000.0, self.total_time)
def title(self):
return _('Resource Usage')
@@ -50,7 +52,7 @@ class TimerDebugPanel(DebugPanel):
return ''
def _elapsed_ru(self, name):
return getattr(self._end_rusage, name) - getattr(self._start_rusage, name)
return (getattr(self._end_rusage, name) - getattr(self._start_rusage, name))
def content(self):
@@ -58,8 +60,8 @@ class TimerDebugPanel(DebugPanel):
stime = 1000 * self._elapsed_ru('ru_stime')
vcsw = self._elapsed_ru('ru_nvcsw')
ivcsw = self._elapsed_ru('ru_nivcsw')
minflt = self._elapsed_ru('ru_minflt')
majflt = self._elapsed_ru('ru_majflt')
# minflt = self._elapsed_ru('ru_minflt')
# majflt = self._elapsed_ru('ru_majflt')
# these are documented as not meaningful under Linux. If you're running BSD
# feel free to enable them, and add any others that I hadn't gotten to before
@@ -80,9 +82,9 @@ class TimerDebugPanel(DebugPanel):
(_('Total CPU time'), '%0.3f msec' % (utime + stime)),
(_('Elapsed time'), '%0.3f msec' % self.total_time),
(_('Context switches'), '%d voluntary, %d involuntary' % (vcsw, ivcsw)),
# ('Memory use', '%d max RSS, %d shared, %d unshared' % (rss, srss, urss + usrss)),
# ('Page faults', '%d no i/o, %d requiring i/o' % (minflt, majflt)),
# ('Disk operations', '%d in, %d out, %d swapout' % (blkin, blkout, swap)),
# ('Memory use', '%d max RSS, %d shared, %d unshared' % (rss, srss, urss + usrss)),
# ('Page faults', '%d no i/o, %d requiring i/o' % (minflt, majflt)),
# ('Disk operations', '%d in, %d out, %d swapout' % (blkin, blkout, swap)),
)
context = self.context.copy()
@@ -91,4 +93,3 @@ class TimerDebugPanel(DebugPanel):
})
return self.render('panels/timer.html', context)

View File

@@ -0,0 +1,52 @@
import os
from distutils.sysconfig import get_python_lib
from flask import __version__ as flask_version
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
def relpath(location, python_lib):
location = os.path.normpath(location)
relative = os.path.relpath(location, python_lib)
if relative == os.path.curdir:
return ''
elif relative.startswith(os.path.pardir):
return location
return relative
class VersionDebugPanel(DebugPanel):
"""
Panel that displays the Flask version.
"""
name = 'Version'
has_content = True
def nav_title(self):
return _('Versions')
def nav_subtitle(self):
return 'Flask %s' % flask_version
def url(self):
return ''
def title(self):
return _('Versions')
def content(self):
try:
import pkg_resources
except ImportError:
packages = []
else:
packages = sorted(pkg_resources.working_set,
key=lambda p: p.project_name.lower())
return self.render('panels/versions.html', {
'packages': packages,
'python_lib': os.path.normpath(get_python_lib()),
'relpath': relpath,
})

View File

@@ -0,0 +1,112 @@
.CodeMirror {
line-height: 1em;
font-family: monospace;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right:none;
width:0;
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #cc7;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: #a0a;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,234 @@
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
keywords = parserConfig.keywords || {},
blockKeywords = parserConfig.blockKeywords || {},
atoms = parserConfig.atoms || {},
hooks = parserConfig.hooks || {},
multiLineStrings = parserConfig.multiLineStrings;
var isOperatorChar = /[+\-*&%=<>!?|\/]/;
var curPunc;
function tokenBase(stream, state) {
var ch = stream.next();
if (hooks[ch]) {
var result = hooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null
}
if (/\d/.test(ch)) {
stream.eatWhile(/[\w\.]/);
return "number";
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
}
if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return "operator";
}
stream.eatWhile(/[\w\$_]/);
var cur = stream.current();
if (keywords.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next, end = false;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) {end = true; break;}
escaped = !escaped && next == "\\";
}
if (end || !(escaped || multiLineStrings))
state.tokenize = null;
return "string";
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}
function Context(indented, column, type, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.align = align;
this.prev = prev;
}
function pushContext(state, col, type) {
return state.context = new Context(state.indented, col, type, null, state.context);
}
function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: null,
context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
indented: 0,
startOfLine: true
};
},
token: function(stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
}
if (stream.eatSpace()) return null;
curPunc = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
pushContext(state, stream.column(), "statement");
state.startOfLine = false;
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase && state.tokenize != null) return 0;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
var closing = firstChar == ctx.type;
if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
else if (ctx.align) return ctx.column + (closing ? 0 : 1);
else return ctx.indented + (closing ? 0 : indentUnit);
},
electricChars: "{}"
};
});
(function() {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
"double static else struct entry switch extern typedef float union for unsigned " +
"goto while enum void const signed volatile";
function cppHook(stream, state) {
if (!state.startOfLine) return false;
stream.skipToEnd();
return "meta";
}
// C#-style strings where "" escapes a quote.
function tokenAtString(stream, state) {
var next;
while ((next = stream.next()) != null) {
if (next == '"' && !stream.eat('"')) {
state.tokenize = null;
break;
}
}
return "string";
}
CodeMirror.defineMIME("text/x-csrc", {
name: "clike",
keywords: words(cKeywords),
blockKeywords: words("case do else for if switch while struct"),
atoms: words("null"),
hooks: {"#": cppHook}
});
CodeMirror.defineMIME("text/x-c++src", {
name: "clike",
keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
"static_cast typeid catch operator template typename class friend private " +
"this using const_cast inline public throw virtual delete mutable protected " +
"wchar_t"),
blockKeywords: words("catch class do else finally for if struct switch try while"),
atoms: words("true false null"),
hooks: {"#": cppHook}
});
CodeMirror.defineMIME("text/x-java", {
name: "clike",
keywords: words("abstract assert boolean break byte case catch char class const continue default " +
"do double else enum extends final finally float for goto if implements import " +
"instanceof int interface long native new package private protected public " +
"return short static strictfp super switch synchronized this throw throws transient " +
"try void volatile while"),
blockKeywords: words("catch class do else finally for if switch try while"),
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
stream.eatWhile(/[\w\$_]/);
return "meta";
}
}
});
CodeMirror.defineMIME("text/x-csharp", {
name: "clike",
keywords: words("abstract as base bool break byte case catch char checked class const continue decimal" +
" default delegate do double else enum event explicit extern finally fixed float for" +
" foreach goto if implicit in int interface internal is lock long namespace new object" +
" operator out override params private protected public readonly ref return sbyte sealed short" +
" sizeof stackalloc static string struct switch this throw try typeof uint ulong unchecked" +
" unsafe ushort using virtual void volatile while add alias ascending descending dynamic from get" +
" global group into join let orderby partial remove select set value var yield"),
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
if (stream.eat('"')) {
state.tokenize = tokenAtString;
return tokenAtString(stream, state);
}
stream.eatWhile(/[\w\$_]/);
return "meta";
}
}
});
}());

View File

@@ -0,0 +1,101 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: C-like mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="clike.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border: 2px inset #dee;}</style>
</head>
<body>
<h1>CodeMirror: C-like mode</h1>
<form><textarea id="code" name="code">
/* C demo code */
#include <zmq.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <malloc.h>
typedef struct {
void* arg_socket;
zmq_msg_t* arg_msg;
char* arg_string;
unsigned long arg_len;
int arg_int, arg_command;
int signal_fd;
int pad;
void* context;
sem_t sem;
} acl_zmq_context;
#define p(X) (context->arg_##X)
void* zmq_thread(void* context_pointer) {
acl_zmq_context* context = (acl_zmq_context*)context_pointer;
char ok = 'K', err = 'X';
int res;
while (1) {
while ((res = sem_wait(&amp;context->sem)) == EINTR);
if (res) {write(context->signal_fd, &amp;err, 1); goto cleanup;}
switch(p(command)) {
case 0: goto cleanup;
case 1: p(socket) = zmq_socket(context->context, p(int)); break;
case 2: p(int) = zmq_close(p(socket)); break;
case 3: p(int) = zmq_bind(p(socket), p(string)); break;
case 4: p(int) = zmq_connect(p(socket), p(string)); break;
case 5: p(int) = zmq_getsockopt(p(socket), p(int), (void*)p(string), &amp;p(len)); break;
case 6: p(int) = zmq_setsockopt(p(socket), p(int), (void*)p(string), p(len)); break;
case 7: p(int) = zmq_send(p(socket), p(msg), p(int)); break;
case 8: p(int) = zmq_recv(p(socket), p(msg), p(int)); break;
case 9: p(int) = zmq_poll(p(socket), p(int), p(len)); break;
}
p(command) = errno;
write(context->signal_fd, &amp;ok, 1);
}
cleanup:
close(context->signal_fd);
free(context_pointer);
return 0;
}
void* zmq_thread_init(void* zmq_context, int signal_fd) {
acl_zmq_context* context = malloc(sizeof(acl_zmq_context));
pthread_t thread;
context->context = zmq_context;
context->signal_fd = signal_fd;
sem_init(&amp;context->sem, 1, 0);
pthread_create(&amp;thread, 0, &amp;zmq_thread, context);
pthread_detach(thread);
return context;
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-csrc"
});
</script>
<p>Simple mode that tries to handle C-like languages as well as it
can. Takes two configuration parameters: <code>keywords</code>, an
object whose property names are the keywords in the language,
and <code>useCPP</code>, which determines whether C preprocessor
directives are recognized.</p>
<p><strong>MIME types defined:</strong> <code>text/x-csrc</code>
(C code), <code>text/x-c++src</code> (C++
code), <code>text/x-java</code> (Java
code), <code>text/x-csharp</code> (C#).</p>
</body>
</html>

View File

@@ -0,0 +1,207 @@
/**
* Author: Hans Engel
* Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
*/
CodeMirror.defineMode("clojure", function (config, mode) {
var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", TAG = "tag",
ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword";
var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1;
function makeKeywords(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
var atoms = makeKeywords("true false nil");
var keywords = makeKeywords(
"defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
var builtins = makeKeywords(
"* *1 *2 *3 *agent* *allow-unresolved-vars* *assert *clojure-version* *command-line-args* *compile-files* *compile-path* *e *err* *file* *flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *use-context-classloader* *warn-on-reflection* + - / < <= = == > >= accessor aclone agent agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec decimal? declare definline defmacro defmethod defmulti defn defn- defonce defstruct delay delay? deliver deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall doc dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq eval even? every? extend extend-protocol extend-type extends? extenders false? ffirst file-seq filter find find-doc find-ns find-var first float float-array float? floats flush fn fn? fnext for force format future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator hash hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map? mapcat max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod name namespace neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? or parents partial partition pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-doc print-dup print-method print-namespace-doc print-simple print-special-doc print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string reify reduce ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure release-pending-sends rem remove remove-method remove-ns repeat repeatedly replace replicate require reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-validator! set? short short-array shorts shutdown-agents slurp some sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-form-anchor special-symbol? split-at split-with str stream? string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync syntax-symbol-anchor take take-last take-nth take-while test the-ns time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-dec unchecked-divide unchecked-inc unchecked-multiply unchecked-negate unchecked-remainder unchecked-subtract underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision xml-seq");
var indentKeys = makeKeywords(
// Built-ins
"ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +
// Binding forms
"let letfn binding loop for doseq dotimes when-let if-let " +
// Data structures
"defstruct struct-map assoc " +
// clojure.test
"testing deftest " +
// contrib
"handler-case handle dotrace deftrace");
var tests = {
digit: /\d/,
digit_or_colon: /[\d:]/,
hex: /[0-9a-fA-F]/,
sign: /[+-]/,
exponent: /[eE]/,
keyword_char: /[^\s\(\[\;\)\]]/,
basic: /[\w\$_\-]/,
lang_keyword: /[\w*+!\-_?:\/]/
};
function stateStack(indent, type, prev) { // represents a state stack object
this.indent = indent;
this.type = type;
this.prev = prev;
}
function pushStack(state, indent, type) {
state.indentStack = new stateStack(indent, type, state.indentStack);
}
function popStack(state) {
state.indentStack = state.indentStack.prev;
}
function isNumber(ch, stream){
// hex
if ( ch === '0' && 'x' == stream.peek().toLowerCase() ) {
stream.eat('x');
stream.eatWhile(tests.hex);
return true;
}
// leading sign
if ( ch == '+' || ch == '-' ) {
stream.eat(tests.sign);
ch = stream.next();
}
if ( tests.digit.test(ch) ) {
stream.eat(ch);
stream.eatWhile(tests.digit);
if ( '.' == stream.peek() ) {
stream.eat('.');
stream.eatWhile(tests.digit);
}
if ( 'e' == stream.peek().toLowerCase() ) {
stream.eat(tests.exponent);
stream.eat(tests.sign);
stream.eatWhile(tests.digit);
}
return true;
}
return false;
}
return {
startState: function () {
return {
indentStack: null,
indentation: 0,
mode: false
};
},
token: function (stream, state) {
if (state.indentStack == null && stream.sol()) {
// update indentation, but only if indentStack is empty
state.indentation = stream.indentation();
}
// skip spaces
if (stream.eatSpace()) {
return null;
}
var returnType = null;
switch(state.mode){
case "string": // multi-line string parsing mode
var next, escaped = false;
while ((next = stream.next()) != null) {
if (next == "\"" && !escaped) {
state.mode = false;
break;
}
escaped = !escaped && next == "\\";
}
returnType = STRING; // continue on in string mode
break;
default: // default parsing mode
var ch = stream.next();
if (ch == "\"") {
state.mode = "string";
returnType = STRING;
} else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
returnType = ATOM;
} else if (ch == ";") { // comment
stream.skipToEnd(); // rest of the line is a comment
returnType = COMMENT;
} else if (isNumber(ch,stream)){
returnType = NUMBER;
} else if (ch == "(" || ch == "[") {
var keyWord = ''; var indentTemp = stream.column();
/**
Either
(indent-word ..
(non-indent-word ..
(;something else, bracket, etc.
*/
if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
keyWord += letter;
}
if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word
pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
} else { // non-indent word
// we continue eating the spaces
stream.eatSpace();
if (stream.eol() || stream.peek() == ";") {
// nothing significant after
// we restart indentation 1 space after
pushStack(state, indentTemp + 1, ch);
} else {
pushStack(state, indentTemp + stream.current().length, ch); // else we match
}
}
stream.backUp(stream.current().length - 1); // undo all the eating
returnType = BRACKET;
} else if (ch == ")" || ch == "]") {
returnType = BRACKET;
if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) {
popStack(state);
}
} else if ( ch == ":" ) {
stream.eatWhile(tests.lang_keyword);
return ATOM;
} else {
stream.eatWhile(tests.basic);
if (keywords && keywords.propertyIsEnumerable(stream.current())) {
returnType = KEYWORD;
} else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
returnType = BUILTIN;
} else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
returnType = ATOM;
} else returnType = null;
}
}
return returnType;
},
indent: function (state, textAfter) {
if (state.indentStack == null) return state.indentation;
return state.indentStack.indent;
}
};
});
CodeMirror.defineMIME("text/x-clojure", "clojure");

View File

@@ -0,0 +1,66 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Clojure mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="clojure.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Clojure mode</h1>
<form><textarea id="code" name="code">
; Conway's Game of Life, based on the work of:
;; Laurent Petit https://gist.github.com/1200343
;; Christophe Grand http://clj-me.cgrand.net/2011/08/19/conways-game-of-life
(ns ^{:doc "Conway's Game of Life."}
game-of-life)
;; Core game of life's algorithm functions
(defn neighbours
"Given a cell's coordinates, returns the coordinates of its neighbours."
[[x y]]
(for [dx [-1 0 1] dy (if (zero? dx) [-1 1] [-1 0 1])]
[(+ dx x) (+ dy y)]))
(defn step
"Given a set of living cells, computes the new set of living cells."
[cells]
(set (for [[cell n] (frequencies (mapcat neighbours cells))
:when (or (= n 3) (and (= n 2) (cells cell)))]
cell)))
;; Utility methods for displaying game on a text terminal
(defn print-board
"Prints a board on *out*, representing a step in the game."
[board w h]
(doseq [x (range (inc w)) y (range (inc h))]
(if (= y 0) (print "\n"))
(print (if (board [x y]) "[X]" " . "))))
(defn display-grids
"Prints a squence of boards on *out*, representing several steps."
[grids w h]
(doseq [board grids]
(print-board board w h)
(print "\n")))
;; Launches an example board
(def
^{:doc "board represents the initial set of living cells"}
board #{[2 1] [2 2] [2 3]})
(display-grids (take 3 (iterate step board)) 5 5) </textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-clojure</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,22 @@
The MIT License
Copyright (c) 2011 Jeff Pickhardt
Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,341 @@
/**
* Link to the project's GitHub page:
* https://github.com/pickhardt/coffeescript-codemirror-mode
*/
CodeMirror.defineMode('coffeescript', function(conf) {
var ERRORCLASS = 'error';
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
}
var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]");
var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))");
var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))");
var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*");
var wordOperators = wordRegexp(['and', 'or', 'not',
'is', 'isnt', 'in',
'instanceof', 'typeof']);
var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else',
'switch', 'try', 'catch', 'finally', 'class'];
var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete',
'do', 'in', 'of', 'new', 'return', 'then',
'this', 'throw', 'when', 'until'];
var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
indentKeywords = wordRegexp(indentKeywords);
var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])");
var regexPrefixes = new RegExp("^(/{3}|/)");
var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no'];
var constants = wordRegexp(commonConstants);
// Tokenizers
function tokenBase(stream, state) {
// Handle scope changes
if (stream.sol()) {
var scopeOffset = state.scopes[0].offset;
if (stream.eatSpace()) {
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset) {
return 'indent';
} else if (lineOffset < scopeOffset) {
return 'dedent';
}
return null;
} else {
if (scopeOffset > 0) {
dedent(stream, state);
}
}
}
if (stream.eatSpace()) {
return null;
}
var ch = stream.peek();
// Handle multi line comments
if (stream.match("###")) {
state.tokenize = longComment;
return state.tokenize(stream, state);
}
// Single line comment
if (ch === '#') {
stream.skipToEnd();
return 'comment';
}
// Handle number literals
if (stream.match(/^-?[0-9\.]/, false)) {
var floatLiteral = false;
// Floats
if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
floatLiteral = true;
}
if (stream.match(/^-?\d+\.\d*/)) {
floatLiteral = true;
}
if (stream.match(/^-?\.\d+/)) {
floatLiteral = true;
}
if (floatLiteral) {
// prevent from getting extra . on 1..
if (stream.peek() == "."){
stream.backUp(1);
}
return 'number';
}
// Integers
var intLiteral = false;
// Hex
if (stream.match(/^-?0x[0-9a-f]+/i)) {
intLiteral = true;
}
// Decimal
if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
intLiteral = true;
}
// Zero by itself with no other piece of number.
if (stream.match(/^-?0(?![\dx])/i)) {
intLiteral = true;
}
if (intLiteral) {
return 'number';
}
}
// Handle strings
if (stream.match(stringPrefixes)) {
state.tokenize = tokenFactory(stream.current(), 'string');
return state.tokenize(stream, state);
}
// Handle regex literals
if (stream.match(regexPrefixes)) {
if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division
state.tokenize = tokenFactory(stream.current(), 'string-2');
return state.tokenize(stream, state);
} else {
stream.backUp(1);
}
}
// Handle operators and delimiters
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
return 'punctuation';
}
if (stream.match(doubleOperators)
|| stream.match(singleOperators)
|| stream.match(wordOperators)) {
return 'operator';
}
if (stream.match(singleDelimiters)) {
return 'punctuation';
}
if (stream.match(constants)) {
return 'atom';
}
if (stream.match(keywords)) {
return 'keyword';
}
if (stream.match(identifiers)) {
return 'variable';
}
// Handle non-detected items
stream.next();
return ERRORCLASS;
}
function tokenFactory(delimiter, outclass) {
var singleline = delimiter.length == 1;
return function tokenString(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^'"\/\\]/);
if (stream.eat('\\')) {
stream.next();
if (singleline && stream.eol()) {
return outclass;
}
} else if (stream.match(delimiter)) {
state.tokenize = tokenBase;
return outclass;
} else {
stream.eat(/['"\/]/);
}
}
if (singleline) {
if (conf.mode.singleLineStringErrors) {
outclass = ERRORCLASS
} else {
state.tokenize = tokenBase;
}
}
return outclass;
};
}
function longComment(stream, state) {
while (!stream.eol()) {
stream.eatWhile(/[^#]/);
if (stream.match("###")) {
state.tokenize = tokenBase;
break;
}
stream.eatWhile("#");
}
return "comment"
}
function indent(stream, state, type) {
type = type || 'coffee';
var indentUnit = 0;
if (type === 'coffee') {
for (var i = 0; i < state.scopes.length; i++) {
if (state.scopes[i].type === 'coffee') {
indentUnit = state.scopes[i].offset + conf.indentUnit;
break;
}
}
} else {
indentUnit = stream.column() + stream.current().length;
}
state.scopes.unshift({
offset: indentUnit,
type: type
});
}
function dedent(stream, state) {
if (state.scopes.length == 1) return;
if (state.scopes[0].type === 'coffee') {
var _indent = stream.indentation();
var _indent_index = -1;
for (var i = 0; i < state.scopes.length; ++i) {
if (_indent === state.scopes[i].offset) {
_indent_index = i;
break;
}
}
if (_indent_index === -1) {
return true;
}
while (state.scopes[0].offset !== _indent) {
state.scopes.shift();
}
return false
} else {
state.scopes.shift();
return false;
}
}
function tokenLexer(stream, state) {
var style = state.tokenize(stream, state);
var current = stream.current();
// Handle '.' connected identifiers
if (current === '.') {
style = state.tokenize(stream, state);
current = stream.current();
if (style === 'variable') {
return 'variable';
} else {
return ERRORCLASS;
}
}
// Handle properties
if (current === '@') {
stream.eat('@');
return 'keyword';
}
// Handle scope changes.
if (current === 'return') {
state.dedent += 1;
}
if (((current === '->' || current === '=>') &&
!state.lambda &&
state.scopes[0].type == 'coffee' &&
stream.peek() === '')
|| style === 'indent') {
indent(stream, state);
}
var delimiter_index = '[({'.indexOf(current);
if (delimiter_index !== -1) {
indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
}
if (indentKeywords.exec(current)){
indent(stream, state);
}
if (current == 'then'){
dedent(stream, state);
}
if (style === 'dedent') {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}
delimiter_index = '])}'.indexOf(current);
if (delimiter_index !== -1) {
if (dedent(stream, state)) {
return ERRORCLASS;
}
}
if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') {
if (state.scopes.length > 1) state.scopes.shift();
state.dedent -= 1;
}
return style;
}
var external = {
startState: function(basecolumn) {
return {
tokenize: tokenBase,
scopes: [{offset:basecolumn || 0, type:'coffee'}],
lastToken: null,
lambda: false,
dedent: 0
};
},
token: function(stream, state) {
var style = tokenLexer(stream, state);
state.lastToken = {style:style, content: stream.current()};
if (stream.eol() && stream.lambda) {
state.lambda = false;
}
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase) {
return 0;
}
return state.scopes[0].offset;
}
};
return external;
});
CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript');

View File

@@ -0,0 +1,721 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: CoffeeScript mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="coffeescript.js"></script>
<style>.CodeMirror {border-top: 1px solid silver; border-bottom: 1px solid silver;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: CoffeeScript mode</h1>
<form><textarea id="code" name="code">
# CoffeeScript mode for CodeMirror
# Copyright (c) 2011 Jeff Pickhardt, released under
# the MIT License.
#
# Modified from the Python CodeMirror mode, which also is
# under the MIT License Copyright (c) 2010 Timothy Farrell.
#
# The following script, Underscore.coffee, is used to
# demonstrate CoffeeScript mode for CodeMirror.
#
# To download CoffeeScript mode for CodeMirror, go to:
# https://github.com/pickhardt/coffeescript-codemirror-mode
# **Underscore.coffee
# (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.**
# Underscore is freely distributable under the terms of the
# [MIT license](http://en.wikipedia.org/wiki/MIT_License).
# Portions of Underscore are inspired by or borrowed from
# [Prototype.js](http://prototypejs.org/api), Oliver Steele's
# [Functional](http://osteele.com), and John Resig's
# [Micro-Templating](http://ejohn.org).
# For all details and documentation:
# http://documentcloud.github.com/underscore/
# Baseline setup
# --------------
# Establish the root object, `window` in the browser, or `global` on the server.
root = this
# Save the previous value of the `_` variable.
previousUnderscore = root._
# Establish the object that gets thrown to break out of a loop iteration.
# `StopIteration` is SOP on Mozilla.
breaker = if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
# Helper function to escape **RegExp** contents, because JS doesn't have one.
escapeRegExp = (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
# Save bytes in the minified (but not gzipped) version:
ArrayProto = Array.prototype
ObjProto = Object.prototype
# Create quick reference variables for speed access to core prototypes.
slice = ArrayProto.slice
unshift = ArrayProto.unshift
toString = ObjProto.toString
hasOwnProperty = ObjProto.hasOwnProperty
propertyIsEnumerable = ObjProto.propertyIsEnumerable
# All **ECMA5** native implementations we hope to use are declared here.
nativeForEach = ArrayProto.forEach
nativeMap = ArrayProto.map
nativeReduce = ArrayProto.reduce
nativeReduceRight = ArrayProto.reduceRight
nativeFilter = ArrayProto.filter
nativeEvery = ArrayProto.every
nativeSome = ArrayProto.some
nativeIndexOf = ArrayProto.indexOf
nativeLastIndexOf = ArrayProto.lastIndexOf
nativeIsArray = Array.isArray
nativeKeys = Object.keys
# Create a safe reference to the Underscore object for use below.
_ = (obj) -> new wrapper(obj)
# Export the Underscore object for **CommonJS**.
if typeof(exports) != 'undefined' then exports._ = _
# Export Underscore to global scope.
root._ = _
# Current version.
_.VERSION = '1.1.0'
# Collection Functions
# --------------------
# The cornerstone, an **each** implementation.
# Handles objects implementing **forEach**, arrays, and raw objects.
_.each = (obj, iterator, context) ->
try
if nativeForEach and obj.forEach is nativeForEach
obj.forEach iterator, context
else if _.isNumber obj.length
iterator.call context, obj[i], i, obj for i in [0...obj.length]
else
iterator.call context, val, key, obj for own key, val of obj
catch e
throw e if e isnt breaker
obj
# Return the results of applying the iterator to each element. Use JavaScript
# 1.6's version of **map**, if possible.
_.map = (obj, iterator, context) ->
return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
results = []
_.each obj, (value, index, list) ->
results.push iterator.call context, value, index, list
results
# **Reduce** builds up a single result from a list of values. Also known as
# **inject**, or **foldl**. Uses JavaScript 1.8's version of **reduce**, if possible.
_.reduce = (obj, iterator, memo, context) ->
if nativeReduce and obj.reduce is nativeReduce
iterator = _.bind iterator, context if context
return obj.reduce iterator, memo
_.each obj, (value, index, list) ->
memo = iterator.call context, memo, value, index, list
memo
# The right-associative version of **reduce**, also known as **foldr**. Uses
# JavaScript 1.8's version of **reduceRight**, if available.
_.reduceRight = (obj, iterator, memo, context) ->
if nativeReduceRight and obj.reduceRight is nativeReduceRight
iterator = _.bind iterator, context if context
return obj.reduceRight iterator, memo
reversed = _.clone(_.toArray(obj)).reverse()
_.reduce reversed, iterator, memo, context
# Return the first value which passes a truth test.
_.detect = (obj, iterator, context) ->
result = null
_.each obj, (value, index, list) ->
if iterator.call context, value, index, list
result = value
_.breakLoop()
result
# Return all the elements that pass a truth test. Use JavaScript 1.6's
# **filter**, if it exists.
_.filter = (obj, iterator, context) ->
return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
results = []
_.each obj, (value, index, list) ->
results.push value if iterator.call context, value, index, list
results
# Return all the elements for which a truth test fails.
_.reject = (obj, iterator, context) ->
results = []
_.each obj, (value, index, list) ->
results.push value if not iterator.call context, value, index, list
results
# Determine whether all of the elements match a truth test. Delegate to
# JavaScript 1.6's **every**, if it is present.
_.every = (obj, iterator, context) ->
iterator ||= _.identity
return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
result = true
_.each obj, (value, index, list) ->
_.breakLoop() unless (result = result and iterator.call(context, value, index, list))
result
# Determine if at least one element in the object matches a truth test. Use
# JavaScript 1.6's **some**, if it exists.
_.some = (obj, iterator, context) ->
iterator ||= _.identity
return obj.some iterator, context if nativeSome and obj.some is nativeSome
result = false
_.each obj, (value, index, list) ->
_.breakLoop() if (result = iterator.call(context, value, index, list))
result
# Determine if a given value is included in the array or object,
# based on `===`.
_.include = (obj, target) ->
return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
return true for own key, val of obj when val is target
false
# Invoke a method with arguments on every item in a collection.
_.invoke = (obj, method) ->
args = _.rest arguments, 2
(if method then val[method] else val).apply(val, args) for val in obj
# Convenience version of a common use case of **map**: fetching a property.
_.pluck = (obj, key) ->
_.map(obj, (val) -> val[key])
# Return the maximum item or (item-based computation).
_.max = (obj, iterator, context) ->
return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
result = computed: -Infinity
_.each obj, (value, index, list) ->
computed = if iterator then iterator.call(context, value, index, list) else value
computed >= result.computed and (result = {value: value, computed: computed})
result.value
# Return the minimum element (or element-based computation).
_.min = (obj, iterator, context) ->
return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
result = computed: Infinity
_.each obj, (value, index, list) ->
computed = if iterator then iterator.call(context, value, index, list) else value
computed < result.computed and (result = {value: value, computed: computed})
result.value
# Sort the object's values by a criterion produced by an iterator.
_.sortBy = (obj, iterator, context) ->
_.pluck(((_.map obj, (value, index, list) ->
{value: value, criteria: iterator.call(context, value, index, list)}
).sort((left, right) ->
a = left.criteria; b = right.criteria
if a < b then -1 else if a > b then 1 else 0
)), 'value')
# Use a comparator function to figure out at what index an object should
# be inserted so as to maintain order. Uses binary search.
_.sortedIndex = (array, obj, iterator) ->
iterator ||= _.identity
low = 0
high = array.length
while low < high
mid = (low + high) >> 1
if iterator(array[mid]) < iterator(obj) then low = mid + 1 else high = mid
low
# Convert anything iterable into a real, live array.
_.toArray = (iterable) ->
return [] if (!iterable)
return iterable.toArray() if (iterable.toArray)
return iterable if (_.isArray(iterable))
return slice.call(iterable) if (_.isArguments(iterable))
_.values(iterable)
# Return the number of elements in an object.
_.size = (obj) -> _.toArray(obj).length
# Array Functions
# ---------------
# Get the first element of an array. Passing `n` will return the first N
# values in the array. Aliased as **head**. The `guard` check allows it to work
# with **map**.
_.first = (array, n, guard) ->
if n and not guard then slice.call(array, 0, n) else array[0]
# Returns everything but the first entry of the array. Aliased as **tail**.
# Especially useful on the arguments object. Passing an `index` will return
# the rest of the values in the array from that index onward. The `guard`
# check allows it to work with **map**.
_.rest = (array, index, guard) ->
slice.call(array, if _.isUndefined(index) or guard then 1 else index)
# Get the last element of an array.
_.last = (array) -> array[array.length - 1]
# Trim out all falsy values from an array.
_.compact = (array) -> item for item in array when item
# Return a completely flattened version of an array.
_.flatten = (array) ->
_.reduce array, (memo, value) ->
return memo.concat(_.flatten(value)) if _.isArray value
memo.push value
memo
, []
# Return a version of the array that does not contain the specified value(s).
_.without = (array) ->
values = _.rest arguments
val for val in _.toArray(array) when not _.include values, val
# Produce a duplicate-free version of the array. If the array has already
# been sorted, you have the option of using a faster algorithm.
_.uniq = (array, isSorted) ->
memo = []
for el, i in _.toArray array
memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
memo
# Produce an array that contains every item shared between all the
# passed-in arrays.
_.intersect = (array) ->
rest = _.rest arguments
_.select _.uniq(array), (item) ->
_.all rest, (other) ->
_.indexOf(other, item) >= 0
# Zip together multiple lists into a single array -- elements that share
# an index go together.
_.zip = ->
length = _.max _.pluck arguments, 'length'
results = new Array length
for i in [0...length]
results[i] = _.pluck arguments, String i
results
# If the browser doesn't supply us with **indexOf** (I'm looking at you, MSIE),
# we need this function. Return the position of the first occurrence of an
# item in an array, or -1 if the item is not included in the array.
_.indexOf = (array, item) ->
return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
i = 0; l = array.length
while l - i
if array[i] is item then return i else i++
-1
# Provide JavaScript 1.6's **lastIndexOf**, delegating to the native function,
# if possible.
_.lastIndexOf = (array, item) ->
return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
i = array.length
while i
if array[i] is item then return i else i--
-1
# Generate an integer Array containing an arithmetic progression. A port of
# [the native Python **range** function](http://docs.python.org/library/functions.html#range).
_.range = (start, stop, step) ->
a = arguments
solo = a.length <= 1
i = start = if solo then 0 else a[0]
stop = if solo then a[0] else a[1]
step = a[2] or 1
len = Math.ceil((stop - start) / step)
return [] if len <= 0
range = new Array len
idx = 0
loop
return range if (if step > 0 then i - stop else stop - i) >= 0
range[idx] = i
idx++
i+= step
# Function Functions
# ------------------
# Create a function bound to a given object (assigning `this`, and arguments,
# optionally). Binding with arguments is also known as **curry**.
_.bind = (func, obj) ->
args = _.rest arguments, 2
-> func.apply obj or root, args.concat arguments
# Bind all of an object's methods to that object. Useful for ensuring that
# all callbacks defined on an object belong to it.
_.bindAll = (obj) ->
funcs = if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
_.each funcs, (f) -> obj[f] = _.bind obj[f], obj
obj
# Delays a function for the given number of milliseconds, and then calls
# it with the arguments supplied.
_.delay = (func, wait) ->
args = _.rest arguments, 2
setTimeout((-> func.apply(func, args)), wait)
# Memoize an expensive function by storing its results.
_.memoize = (func, hasher) ->
memo = {}
hasher or= _.identity
->
key = hasher.apply this, arguments
return memo[key] if key of memo
memo[key] = func.apply this, arguments
# Defers a function, scheduling it to run after the current call stack has
# cleared.
_.defer = (func) ->
_.delay.apply _, [func, 1].concat _.rest arguments
# Returns the first function passed as an argument to the second,
# allowing you to adjust arguments, run code before and after, and
# conditionally execute the original function.
_.wrap = (func, wrapper) ->
-> wrapper.apply wrapper, [func].concat arguments
# Returns a function that is the composition of a list of functions, each
# consuming the return value of the function that follows.
_.compose = ->
funcs = arguments
->
args = arguments
for i in [funcs.length - 1..0] by -1
args = [funcs[i].apply(this, args)]
args[0]
# Object Functions
# ----------------
# Retrieve the names of an object's properties.
_.keys = nativeKeys or (obj) ->
return _.range 0, obj.length if _.isArray(obj)
key for key, val of obj
# Retrieve the values of an object's properties.
_.values = (obj) ->
_.map obj, _.identity
# Return a sorted list of the function names available in Underscore.
_.functions = (obj) ->
_.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
# Extend a given object with all of the properties in a source object.
_.extend = (obj) ->
for source in _.rest(arguments)
obj[key] = val for key, val of source
obj
# Create a (shallow-cloned) duplicate of an object.
_.clone = (obj) ->
return obj.slice 0 if _.isArray obj
_.extend {}, obj
# Invokes interceptor with the obj, and then returns obj.
# The primary purpose of this method is to "tap into" a method chain,
# in order to perform operations on intermediate results within
the chain.
_.tap = (obj, interceptor) ->
interceptor obj
obj
# Perform a deep comparison to check if two objects are equal.
_.isEqual = (a, b) ->
# Check object identity.
return true if a is b
# Different types?
atype = typeof(a); btype = typeof(b)
return false if atype isnt btype
# Basic equality test (watch out for coercions).
return true if `a == b`
# One is falsy and the other truthy.
return false if (!a and b) or (a and !b)
# One of them implements an `isEqual()`?
return a.isEqual(b) if a.isEqual
# Check dates' integer values.
return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b)
# Both are NaN?
return false if _.isNaN(a) and _.isNaN(b)
# Compare regular expressions.
if _.isRegExp(a) and _.isRegExp(b)
return a.source is b.source and
a.global is b.global and
a.ignoreCase is b.ignoreCase and
a.multiline is b.multiline
# If a is not an object by this point, we can't handle it.
return false if atype isnt 'object'
# Check for different array lengths before comparing contents.
return false if a.length and (a.length isnt b.length)
# Nothing else worked, deep compare the contents.
aKeys = _.keys(a); bKeys = _.keys(b)
# Different object sizes?
return false if aKeys.length isnt bKeys.length
# Recursive comparison of contents.
return false for key, val of a when !(key of b) or !_.isEqual(val, b[key])
true
# Is a given array or object empty?
_.isEmpty = (obj) ->
return obj.length is 0 if _.isArray(obj) or _.isString(obj)
return false for own key of obj
true
# Is a given value a DOM element?
_.isElement = (obj) -> obj and obj.nodeType is 1
# Is a given value an array?
_.isArray = nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift and not obj.callee)
# Is a given variable an arguments object?
_.isArguments = (obj) -> obj and obj.callee
# Is the given value a function?
_.isFunction = (obj) -> !!(obj and obj.constructor and obj.call and obj.apply)
# Is the given value a string?
_.isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
# Is a given value a number?
_.isNumber = (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
# Is a given value a boolean?
_.isBoolean = (obj) -> obj is true or obj is false
# Is a given value a Date?
_.isDate = (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
# Is the given value a regular expression?
_.isRegExp = (obj) -> !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false))
# Is the given value NaN -- this one is interesting. `NaN != NaN`, and
# `isNaN(undefined) == true`, so we make sure it's a number first.
_.isNaN = (obj) -> _.isNumber(obj) and window.isNaN(obj)
# Is a given value equal to null?
_.isNull = (obj) -> obj is null
# Is a given variable undefined?
_.isUndefined = (obj) -> typeof obj is 'undefined'
# Utility Functions
# -----------------
# Run Underscore.js in noConflict mode, returning the `_` variable to its
# previous owner. Returns a reference to the Underscore object.
_.noConflict = ->
root._ = previousUnderscore
this
# Keep the identity function around for default iterators.
_.identity = (value) -> value
# Run a function `n` times.
_.times = (n, iterator, context) ->
iterator.call context, i for i in [0...n]
# Break out of the middle of an iteration.
_.breakLoop = -> throw breaker
# Add your own custom functions to the Underscore object, ensuring that
# they're correctly added to the OOP wrapper as well.
_.mixin = (obj) ->
for name in _.functions(obj)
addToWrapper name, _[name] = obj[name]
# Generate a unique integer id (unique within the entire client session).
# Useful for temporary DOM ids.
idCounter = 0
_.uniqueId = (prefix) ->
(prefix or '') + idCounter++
# By default, Underscore uses **ERB**-style template delimiters, change the
# following template settings to use alternative delimiters.
_.templateSettings = {
start: '<%'
end: '%>'
interpolate: /<%=(.+?)%>/g
}
# JavaScript templating a-la **ERB**, pilfered from John Resig's
# *Secrets of the JavaScript Ninja*, page 83.
# Single-quote fix from Rick Strahl.
# With alterations for arbitrary delimiters, and to preserve whitespace.
_.template = (str, data) ->
c = _.templateSettings
endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
fn = new Function 'obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj||{}){p.push(\'' +
str.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
.replace(endMatch,"<22><><EFBFBD>")
.split("'").join("\\'")
.split("<22><><EFBFBD>").join("'")
.replace(c.interpolate, "',$1,'")
.split(c.start).join("');")
.split(c.end).join("p.push('") +
"');}return p.join('');"
if data then fn(data) else fn
# Aliases
# -------
_.forEach = _.each
_.foldl = _.inject = _.reduce
_.foldr = _.reduceRight
_.select = _.filter
_.all = _.every
_.any = _.some
_.contains = _.include
_.head = _.first
_.tail = _.rest
_.methods = _.functions
# Setup the OOP Wrapper
# ---------------------
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper = (obj) ->
this._wrapped = obj
this
# Helper function to continue chaining intermediate results.
result = (obj, chain) ->
if chain then _(obj).chain() else obj
# A method to easily add functions to the OOP wrapper.
addToWrapper = (name, func) ->
wrapper.prototype[name] = ->
args = _.toArray arguments
unshift.call args, this._wrapped
result func.apply(_, args), this._chain
# Add all ofthe Underscore functions to the wrapper object.
_.mixin _
# Add all mutator Array functions to the wrapper.
_.each ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], (name) ->
method = Array.prototype[name]
wrapper.prototype[name] = ->
method.apply(this._wrapped, arguments)
result(this._wrapped, this._chain)
# Add all accessor Array functions to the wrapper.
_.each ['concat', 'join', 'slice'], (name) ->
method = Array.prototype[name]
wrapper.prototype[name] = ->
result(method.apply(this._wrapped, arguments), this._chain)
# Start chaining a wrapped Underscore object.
wrapper::chain = ->
this._chain = true
this
# Extracts the result from a wrapped and chained object.
wrapper::value = -> this._wrapped
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-coffeescript</code>.</p>
<p>The CoffeeScript mode was written by Jeff Pickhardt (<a href="LICENSE">license</a>).</p>
</body>
</html>

View File

@@ -0,0 +1,124 @@
CodeMirror.defineMode("css", function(config) {
var indentUnit = config.indentUnit, type;
function ret(style, tp) {type = tp; return style;}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());}
else if (ch == "/" && stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
else if (ch == "<" && stream.eat("!")) {
state.tokenize = tokenSGMLComment;
return tokenSGMLComment(stream, state);
}
else if (ch == "=") ret(null, "compare");
else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
}
else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
}
else if (/\d/.test(ch)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
}
else if (/[,.+>*\/]/.test(ch)) {
return ret(null, "select-op");
}
else if (/[;{}:\[\]]/.test(ch)) {
return ret(null, ch);
}
else {
stream.eatWhile(/[\w\\\-]/);
return ret("variable", "variable");
}
}
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenSGMLComment(stream, state) {
var dashes = 0, ch;
while ((ch = stream.next()) != null) {
if (dashes >= 2 && ch == ">") {
state.tokenize = tokenBase;
break;
}
dashes = (ch == "-") ? dashes + 1 : 0;
}
return ret("comment", "comment");
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped)
break;
escaped = !escaped && ch == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
return {
startState: function(base) {
return {tokenize: tokenBase,
baseIndent: base || 0,
stack: []};
},
token: function(stream, state) {
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
var context = state.stack[state.stack.length-1];
if (type == "hash" && context != "rule") style = "string-2";
else if (style == "variable") {
if (context == "rule") style = "number";
else if (!context || context == "@media{") style = "tag";
}
if (context == "rule" && /^[\{\};]$/.test(type))
state.stack.pop();
if (type == "{") {
if (context == "@media") state.stack[state.stack.length-1] = "@media{";
else state.stack.push("{");
}
else if (type == "}") state.stack.pop();
else if (type == "@media") state.stack.push("@media");
else if (context == "{" && type != "comment") state.stack.push("rule");
return style;
},
indent: function(state, textAfter) {
var n = state.stack.length;
if (/^\}/.test(textAfter))
n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
return state.baseIndent + n * indentUnit;
},
electricChars: "}"
};
});
CodeMirror.defineMIME("text/css", "css");

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: CSS mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="css.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: CSS mode</h1>
<form><textarea id="code" name="code">
/* Some example CSS */
@import url("something.css");
body {
margin: 0;
padding: 3em 6em;
font-family: tahoma, arial, sans-serif;
color: #000;
}
#navigation a {
font-weight: bold;
text-decoration: none !important;
}
h1 {
font-size: 2.5em;
}
h2 {
font-size: 1.7em;
}
h1:before, h2:before {
content: "::";
}
code {
font-family: courier, monospace;
font-size: 80%;
color: #418A8A;
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
</script>
<p><strong>MIME types defined:</strong> <code>text/css</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,3 @@
span.cm-rangeinfo {color: #a0b;}
span.cm-minus {color: red;}
span.cm-plus {color: #2b2;}

View File

@@ -0,0 +1,13 @@
CodeMirror.defineMode("diff", function() {
return {
token: function(stream) {
var ch = stream.next();
stream.skipToEnd();
if (ch == "+") return "plus";
if (ch == "-") return "minus";
if (ch == "@") return "rangeinfo";
}
};
});
CodeMirror.defineMIME("text/x-diff", "diff");

View File

@@ -0,0 +1,99 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Diff mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="diff.js"></script>
<link rel="stylesheet" href="diff.css">
<style>.CodeMirror {border-top: 1px solid #ddd; border-bottom: 1px solid #ddd;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Diff mode</h1>
<form><textarea id="code" name="code">
diff --git a/index.html b/index.html
index c1d9156..7764744 100644
--- a/index.html
+++ b/index.html
@@ -95,7 +95,8 @@ StringStream.prototype = {
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
- autoMatchBrackets: true
+ autoMatchBrackets: true,
+ onGutterClick: function(x){console.log(x);}
});
</script>
</body>
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 04646a9..9a39cc7 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.js
@@ -399,10 +399,16 @@ var CodeMirror = (function() {
}
function onMouseDown(e) {
- var start = posFromMouse(e), last = start;
+ var start = posFromMouse(e), last = start, target = e.target();
if (!start) return;
setCursor(start.line, start.ch, false);
if (e.button() != 1) return;
+ if (target.parentNode == gutter) {
+ if (options.onGutterClick)
+ options.onGutterClick(indexOf(gutter.childNodes, target) + showingFrom);
+ return;
+ }
+
if (!focused) onFocus();
e.stop();
@@ -808,7 +814,7 @@ var CodeMirror = (function() {
for (var i = showingFrom; i < showingTo; ++i) {
var marker = lines[i].gutterMarker;
if (marker) html.push('<div class="' + marker.style + '">' + htmlEscape(marker.text) + '</div>');
- else html.push("<div>" + (options.lineNumbers ? i + 1 : "\u00a0") + "</div>");
+ else html.push("<div>" + (options.lineNumbers ? i + options.firstLineNumber : "\u00a0") + "</div>");
}
gutter.style.display = "none"; // TODO test whether this actually helps
gutter.innerHTML = html.join("");
@@ -1371,10 +1377,8 @@ var CodeMirror = (function() {
if (option == "parser") setParser(value);
else if (option === "lineNumbers") setLineNumbers(value);
else if (option === "gutter") setGutter(value);
- else if (option === "readOnly") options.readOnly = value;
- else if (option === "indentUnit") {options.indentUnit = indentUnit = value; setParser(options.parser);}
- else if (/^(?:enterMode|tabMode|indentWithTabs|readOnly|autoMatchBrackets|undoDepth)$/.test(option)) options[option] = value;
- else throw new Error("Can't set option " + option);
+ else if (option === "indentUnit") {options.indentUnit = value; setParser(options.parser);}
+ else options[option] = value;
},
cursorCoords: cursorCoords,
undo: operation(undo),
@@ -1402,7 +1406,8 @@ var CodeMirror = (function() {
replaceRange: operation(replaceRange),
operation: function(f){return operation(f)();},
- refresh: function(){updateDisplay([{from: 0, to: lines.length}]);}
+ refresh: function(){updateDisplay([{from: 0, to: lines.length}]);},
+ getInputField: function(){return input;}
};
return instance;
}
@@ -1420,6 +1425,7 @@ var CodeMirror = (function() {
readOnly: false,
onChange: null,
onCursorActivity: null,
+ onGutterClick: null,
autoMatchBrackets: false,
workTime: 200,
workDelay: 300,
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-diff</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,203 @@
CodeMirror.defineMode("ecl", function(config) {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
function metaHook(stream, state) {
if (!state.startOfLine) return false;
stream.skipToEnd();
return "meta";
}
function tokenAtString(stream, state) {
var next;
while ((next = stream.next()) != null) {
if (next == '"' && !stream.eat('"')) {
state.tokenize = null;
break;
}
}
return "string";
}
var indentUnit = config.indentUnit;
var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode");
var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait");
var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath");
var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode");
var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when");
var blockKeywords = words("catch class do else finally for if switch try while");
var atoms = words("true false null");
var hooks = {"#": metaHook};
var multiLineStrings;
var isOperatorChar = /[+\-*&%=<>!?|\/]/;
var curPunc;
function tokenBase(stream, state) {
var ch = stream.next();
if (hooks[ch]) {
var result = hooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null
}
if (/\d/.test(ch)) {
stream.eatWhile(/[\w\.]/);
return "number";
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
}
if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return "operator";
}
stream.eatWhile(/[\w\$_]/);
var cur = stream.current().toLowerCase();
if (keyword.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
} else if (variable.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "variable";
} else if (variable_2.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "variable-2";
} else if (variable_3.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "variable-3";
} else if (builtin.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "builtin";
} else { //Data types are of from KEYWORD##
var i = cur.length - 1;
while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_'))
--i;
if (i > 0) {
var cur2 = cur.substr(0, i + 1);
if (variable_3.propertyIsEnumerable(cur2)) {
if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement";
return "variable-3";
}
}
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next, end = false;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) {end = true; break;}
escaped = !escaped && next == "\\";
}
if (end || !(escaped || multiLineStrings))
state.tokenize = tokenBase;
return "string";
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}
function Context(indented, column, type, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.align = align;
this.prev = prev;
}
function pushContext(state, col, type) {
return state.context = new Context(state.indented, col, type, null, state.context);
}
function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: null,
context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
indented: 0,
startOfLine: true
};
},
token: function(stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
}
if (stream.eatSpace()) return null;
curPunc = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
pushContext(state, stream.column(), "statement");
state.startOfLine = false;
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase && state.tokenize != null) return 0;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
var closing = firstChar == ctx.type;
if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
else if (ctx.align) return ctx.column + (closing ? 0 : 1);
else return ctx.indented + (closing ? 0 : indentUnit);
},
electricChars: "{}"
};
});
CodeMirror.defineMIME("text/x-ecl");

View File

@@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: ECL mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="ecl.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: ECL mode</h1>
<form><textarea id="code" name="code">
/*
sample useless code to demonstrate ecl syntax highlighting
this is a multiline comment!
*/
// this is a singleline comment!
import ut;
r :=
record
string22 s1 := '123';
integer4 i1 := 123;
end;
#option('tmp', true);
d := dataset('tmp::qb', r, thor);
output(d);
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
tabMode: "indent",
matchBrackets: true,
});
</script>
<p>Based on CodeMirror's clike mode. For more information see <a href="http://hpccsystems.com">HPCC Systems</a> web site.</p>
<p><strong>MIME types defined:</strong> <code>text/x-ecl</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,108 @@
CodeMirror.defineMode("gfm", function(config, parserConfig) {
var mdMode = CodeMirror.getMode(config, "markdown");
var aliases = {
html: "htmlmixed",
js: "javascript",
json: "application/json",
c: "text/x-csrc",
"c++": "text/x-c++src",
java: "text/x-java",
csharp: "text/x-csharp",
"c#": "text/x-csharp"
};
// make this lazy so that we don't need to load GFM last
var getMode = (function () {
var i, modes = {}, mimes = {}, mime;
var list = CodeMirror.listModes();
for (i = 0; i < list.length; i++) {
modes[list[i]] = list[i];
}
var mimesList = CodeMirror.listMIMEs();
for (i = 0; i < mimesList.length; i++) {
mime = mimesList[i].mime;
mimes[mime] = mimesList[i].mime;
}
for (var a in aliases) {
if (aliases[a] in modes || aliases[a] in mimes)
modes[a] = aliases[a];
}
return function (lang) {
return modes[lang] ? CodeMirror.getMode(config, modes[lang]) : null;
}
}());
function markdown(stream, state) {
// intercept fenced code blocks
if (stream.sol() && stream.match(/^```([\w+#]*)/)) {
// try switching mode
state.localMode = getMode(RegExp.$1)
if (state.localMode)
state.localState = state.localMode.startState();
state.token = local;
return 'code';
}
return mdMode.token(stream, state.mdState);
}
function local(stream, state) {
if (stream.sol() && stream.match(/^```/)) {
state.localMode = state.localState = null;
state.token = markdown;
return 'code';
}
else if (state.localMode) {
return state.localMode.token(stream, state.localState);
} else {
stream.skipToEnd();
return 'code';
}
}
// custom handleText to prevent emphasis in the middle of a word
// and add autolinking
function handleText(stream, mdState) {
var match;
if (stream.match(/^\w+:\/\/\S+/)) {
return 'linkhref';
}
if (stream.match(/^[^\[*\\<>` _][^\[*\\<>` ]*[^\[*\\<>` _]/)) {
return mdMode.getType(mdState);
}
if (match = stream.match(/^[^\[*\\<>` ]+/)) {
var word = match[0];
if (word[0] === '_' && word[word.length-1] === '_') {
stream.backUp(word.length);
return undefined;
}
return mdMode.getType(mdState);
}
if (stream.eatSpace()) {
return null;
}
}
return {
startState: function() {
var mdState = mdMode.startState();
mdState.text = handleText;
return {token: markdown, mode: "markdown", mdState: mdState,
localMode: null, localState: null};
},
copyState: function(state) {
return {token: state.token, mode: state.mode, mdState: CodeMirror.copyState(mdMode, state.mdState),
localMode: state.localMode,
localState: state.localMode ? CodeMirror.copyState(state.localMode, state.localState) : null};
},
token: function(stream, state) {
return state.token(stream, state);
}
}
});

View File

@@ -0,0 +1,47 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: GFM mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../xml/xml.js"></script>
<script src="../markdown/markdown.js"></script>
<script src="gfm.js"></script>
<script src="../javascript/javascript.js"></script>
<link rel="stylesheet" href="../markdown/markdown.css">
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: GFM mode</h1>
<!-- source: http://daringfireball.net/projects/markdown/basics.text -->
<form><textarea id="code" name="code">
Github Flavored Markdown
========================
Everything from markdown plus GFM features:
## Fenced code blocks
```javascript
for (var i = 0; i &lt; items.length; i++) {
console.log(items[i], i); // log them
}
```
See http://github.github.com/github-flavored-markdown/
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: 'gfm',
lineNumbers: true,
matchBrackets: true,
theme: "default"
});
</script>
</body>
</html>

View File

@@ -0,0 +1,170 @@
CodeMirror.defineMode("go", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var keywords = {
"break":true, "case":true, "chan":true, "const":true, "continue":true,
"default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
"func":true, "go":true, "goto":true, "if":true, "import":true,
"interface":true, "map":true, "package":true, "range":true, "return":true,
"select":true, "struct":true, "switch":true, "type":true, "var":true,
"bool":true, "byte":true, "complex64":true, "complex128":true,
"float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
"int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
"uint64":true, "int":true, "uint":true, "uintptr":true
};
var atoms = {
"true":true, "false":true, "iota":true, "nil":true, "append":true,
"cap":true, "close":true, "complex":true, "copy":true, "imag":true,
"len":true, "make":true, "new":true, "panic":true, "print":true,
"println":true, "real":true, "recover":true
};
var blockKeywords = {
"else":true, "for":true, "func":true, "if":true, "interface":true,
"select":true, "struct":true, "switch":true
};
var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
var curPunc;
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'" || ch == "`") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
if (/[\d\.]/.test(ch)) {
if (ch == ".") {
stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
} else if (ch == "0") {
stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
} else {
stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
}
return "number";
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
}
if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return "operator";
}
stream.eatWhile(/[\w\$_]/);
var cur = stream.current();
if (keywords.propertyIsEnumerable(cur)) {
if (cur == "case" || cur == "default") curPunc = "case";
return "keyword";
}
if (atoms.propertyIsEnumerable(cur)) return "atom";
return "word";
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next, end = false;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) {end = true; break;}
escaped = !escaped && next == "\\";
}
if (end || !(escaped || quote == "`"))
state.tokenize = tokenBase;
return "string";
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}
function Context(indented, column, type, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.align = align;
this.prev = prev;
}
function pushContext(state, col, type) {
return state.context = new Context(state.indented, col, type, null, state.context);
}
function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: null,
context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
indented: 0,
startOfLine: true
};
},
token: function(stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
if (ctx.type == "case") ctx.type = "}";
}
if (stream.eatSpace()) return null;
curPunc = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment") return style;
if (ctx.align == null) ctx.align = true;
if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "case") ctx.type = "case"
else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state);
else if (curPunc == ctx.type) popContext(state);
state.startOfLine = false;
return style;
},
indent: function(state, textAfter) {
if (state.tokenize != tokenBase && state.tokenize != null) return 0;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
state.context.type = "}";
return ctx.indented;
}
var closing = firstChar == ctx.type;
if (ctx.align) return ctx.column + (closing ? 0 : 1);
else return ctx.indented + (closing ? 0 : indentUnit);
},
electricChars: "{}:"
};
});
CodeMirror.defineMIME("text/x-go", "go");

View File

@@ -0,0 +1,72 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Go mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<link rel="stylesheet" href="../../theme/elegant.css">
<script src="../../lib/codemirror.js"></script>
<script src="go.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border:1px solid #999; background:#ffc}</style>
</head>
<body>
<h1>CodeMirror: Go mode</h1>
<form><textarea id="code" name="code">
// Prime Sieve in Go.
// Taken from the Go specification.
// Copyright © The Go Authors.
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ch chan&lt;- int) {
for i := 2; ; i++ {
ch &lt;- i // Send 'i' to channel 'ch'
}
}
// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src &lt;-chan int, dst chan&lt;- int, prime int) {
for i := range src { // Loop over values received from 'src'.
if i%prime != 0 {
dst &lt;- i // Send 'i' to channel 'dst'.
}
}
}
// The prime sieve: Daisy-chain filter processes together.
func sieve() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a subprocess.
for {
prime := &lt;-ch
fmt.Print(prime, "\n")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
func main() {
sieve()
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
theme: "elegant",
matchBrackets: true,
indentUnit: 8,
tabSize: 8,
indentWithTabs: true,
mode: "text/x-go"
});
</script>
<p><strong>MIME type:</strong> <code>text/x-go</code></p>
</body>
</html>

View File

@@ -0,0 +1,210 @@
CodeMirror.defineMode("groovy", function(config, parserConfig) {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
var keywords = words(
"abstract as assert boolean break byte case catch char class const continue def default " +
"do double else enum extends final finally float for goto if implements import in " +
"instanceof int interface long native new package private protected public return " +
"short static strictfp super switch synchronized threadsafe throw throws transient " +
"try void volatile while");
var blockKeywords = words("catch class do else finally for if switch try while enum interface def");
var atoms = words("null true false this");
var curPunc;
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
return startString(ch, stream, state);
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null
}
if (/\d/.test(ch)) {
stream.eatWhile(/[\w\.]/);
if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
return "number";
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize.push(tokenComment);
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
if (expectExpression(state.lastToken)) {
return startString(ch, stream, state);
}
}
if (ch == "-" && stream.eat(">")) {
curPunc = "->";
return null;
}
if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
stream.eatWhile(/[+\-*&%=<>|~]/);
return "operator";
}
stream.eatWhile(/[\w\$_]/);
if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
if (state.lastToken == ".") return "property";
if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
var cur = stream.current();
if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
if (keywords.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
}
return "word";
}
tokenBase.isBase = true;
function startString(quote, stream, state) {
var tripleQuoted = false;
if (quote != "/" && stream.eat(quote)) {
if (stream.eat(quote)) tripleQuoted = true;
else return "string";
}
function t(stream, state) {
var escaped = false, next, end = !tripleQuoted;
while ((next = stream.next()) != null) {
if (next == quote && !escaped) {
if (!tripleQuoted) { break; }
if (stream.match(quote + quote)) { end = true; break; }
}
if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
state.tokenize.push(tokenBaseUntilBrace());
return "string";
}
escaped = !escaped && next == "\\";
}
if (end) state.tokenize.pop();
return "string";
}
state.tokenize.push(t);
return t(stream, state);
}
function tokenBaseUntilBrace() {
var depth = 1;
function t(stream, state) {
if (stream.peek() == "}") {
depth--;
if (depth == 0) {
state.tokenize.pop();
return state.tokenize[state.tokenize.length-1](stream, state);
}
} else if (stream.peek() == "{") {
depth++;
}
return tokenBase(stream, state);
}
t.isBase = true;
return t;
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize.pop();
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}
function expectExpression(last) {
return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
last == "newstatement" || last == "keyword" || last == "proplabel";
}
function Context(indented, column, type, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.align = align;
this.prev = prev;
}
function pushContext(state, col, type) {
return state.context = new Context(state.indented, col, type, null, state.context);
}
function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: [tokenBase],
context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
indented: 0,
startOfLine: true,
lastToken: null
};
},
token: function(stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
// Automatic semicolon insertion
if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
popContext(state); ctx = state.context;
}
}
if (stream.eatSpace()) return null;
curPunc = null;
var style = state.tokenize[state.tokenize.length-1](stream, state);
if (style == "comment") return style;
if (ctx.align == null) ctx.align = true;
if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
// Handle indentation for {x -> \n ... }
else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
popContext(state);
state.context.align = false;
}
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
pushContext(state, stream.column(), "statement");
state.startOfLine = false;
state.lastToken = curPunc || style;
return style;
},
indent: function(state, textAfter) {
if (!state.tokenize[state.tokenize.length-1].isBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
var closing = firstChar == ctx.type;
if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
else if (ctx.align) return ctx.column + (closing ? 0 : 1);
else return ctx.indented + (closing ? 0 : config.indentUnit);
},
electricChars: "{}"
};
});
CodeMirror.defineMIME("text/x-groovy", "groovy");

View File

@@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Groovy mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="groovy.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border-top: 1px solid #500; border-bottom: 1px solid #500;}</style>
</head>
<body>
<h1>CodeMirror: Groovy mode</h1>
<form><textarea id="code" name="code">
//Pattern for groovy script
def p = ~/.*\.groovy/
new File( 'd:\\scripts' ).eachFileMatch(p) {f ->
// imports list
def imports = []
f.eachLine {
// condition to detect an import instruction
ln -> if ( ln =~ '^import .*' ) {
imports << "${ln - 'import '}"
}
}
// print thmen
if ( ! imports.empty ) {
println f
imports.each{ println " $it" }
}
}
/* Coin changer demo code from http://groovy.codehaus.org */
enum UsCoin {
quarter(25), dime(10), nickel(5), penny(1)
UsCoin(v) { value = v }
final value
}
enum OzzieCoin {
fifty(50), twenty(20), ten(10), five(5)
OzzieCoin(v) { value = v }
final value
}
def plural(word, count) {
if (count == 1) return word
word[-1] == 'y' ? word[0..-2] + "ies" : word + "s"
}
def change(currency, amount) {
currency.values().inject([]){ list, coin ->
int count = amount / coin.value
amount = amount % coin.value
list += "$count ${plural(coin.toString(), count)}"
}
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-groovy"
});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-groovy</code></p>
</body>
</html>

View File

@@ -0,0 +1,242 @@
CodeMirror.defineMode("haskell", function(cmCfg, modeCfg) {
function switchState(source, setState, f) {
setState(f);
return f(source, setState);
}
// These should all be Unicode extended, as per the Haskell 2010 report
var smallRE = /[a-z_]/;
var largeRE = /[A-Z]/;
var digitRE = /[0-9]/;
var hexitRE = /[0-9A-Fa-f]/;
var octitRE = /[0-7]/;
var idRE = /[a-z_A-Z0-9']/;
var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/;
var specialRE = /[(),;[\]`{}]/;
var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer
function normal(source, setState) {
if (source.eatWhile(whiteCharRE)) {
return null;
}
var ch = source.next();
if (specialRE.test(ch)) {
if (ch == '{' && source.eat('-')) {
var t = "comment";
if (source.eat('#')) {
t = "meta";
}
return switchState(source, setState, ncomment(t, 1));
}
return null;
}
if (ch == '\'') {
if (source.eat('\\')) {
source.next(); // should handle other escapes here
}
else {
source.next();
}
if (source.eat('\'')) {
return "string";
}
return "error";
}
if (ch == '"') {
return switchState(source, setState, stringLiteral);
}
if (largeRE.test(ch)) {
source.eatWhile(idRE);
if (source.eat('.')) {
return "qualifier";
}
return "variable-2";
}
if (smallRE.test(ch)) {
source.eatWhile(idRE);
return "variable";
}
if (digitRE.test(ch)) {
if (ch == '0') {
if (source.eat(/[xX]/)) {
source.eatWhile(hexitRE); // should require at least 1
return "integer";
}
if (source.eat(/[oO]/)) {
source.eatWhile(octitRE); // should require at least 1
return "number";
}
}
source.eatWhile(digitRE);
var t = "number";
if (source.eat('.')) {
t = "number";
source.eatWhile(digitRE); // should require at least 1
}
if (source.eat(/[eE]/)) {
t = "number";
source.eat(/[-+]/);
source.eatWhile(digitRE); // should require at least 1
}
return t;
}
if (symbolRE.test(ch)) {
if (ch == '-' && source.eat(/-/)) {
source.eatWhile(/-/);
if (!source.eat(symbolRE)) {
source.skipToEnd();
return "comment";
}
}
var t = "variable";
if (ch == ':') {
t = "variable-2";
}
source.eatWhile(symbolRE);
return t;
}
return "error";
}
function ncomment(type, nest) {
if (nest == 0) {
return normal;
}
return function(source, setState) {
var currNest = nest;
while (!source.eol()) {
var ch = source.next();
if (ch == '{' && source.eat('-')) {
++currNest;
}
else if (ch == '-' && source.eat('}')) {
--currNest;
if (currNest == 0) {
setState(normal);
return type;
}
}
}
setState(ncomment(type, currNest));
return type;
}
}
function stringLiteral(source, setState) {
while (!source.eol()) {
var ch = source.next();
if (ch == '"') {
setState(normal);
return "string";
}
if (ch == '\\') {
if (source.eol() || source.eat(whiteCharRE)) {
setState(stringGap);
return "string";
}
if (source.eat('&')) {
}
else {
source.next(); // should handle other escapes here
}
}
}
setState(normal);
return "error";
}
function stringGap(source, setState) {
if (source.eat('\\')) {
return switchState(source, setState, stringLiteral);
}
source.next();
setState(normal);
return "error";
}
var wellKnownWords = (function() {
var wkw = {};
function setType(t) {
return function () {
for (var i = 0; i < arguments.length; i++)
wkw[arguments[i]] = t;
}
}
setType("keyword")(
"case", "class", "data", "default", "deriving", "do", "else", "foreign",
"if", "import", "in", "infix", "infixl", "infixr", "instance", "let",
"module", "newtype", "of", "then", "type", "where", "_");
setType("keyword")(
"\.\.", ":", "::", "=", "\\", "\"", "<-", "->", "@", "~", "=>");
setType("builtin")(
"!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<=", "=<<",
"==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "**");
setType("builtin")(
"Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq",
"False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT",
"IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left",
"Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read",
"ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS",
"String", "True");
setType("builtin")(
"abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf",
"asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling",
"compare", "concat", "concatMap", "const", "cos", "cosh", "curry",
"cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either",
"elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo",
"enumFromTo", "error", "even", "exp", "exponent", "fail", "filter",
"flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap",
"foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger",
"fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents",
"getLine", "head", "id", "init", "interact", "ioError", "isDenormalized",
"isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last",
"lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map",
"mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound",
"minimum", "mod", "negate", "not", "notElem", "null", "odd", "or",
"otherwise", "pi", "pred", "print", "product", "properFraction",
"putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile",
"readIO", "readList", "readLn", "readParen", "reads", "readsPrec",
"realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse",
"round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq",
"sequence", "sequence_", "show", "showChar", "showList", "showParen",
"showString", "shows", "showsPrec", "significand", "signum", "sin",
"sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum",
"tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger",
"toRational", "truncate", "uncurry", "undefined", "unlines", "until",
"unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip",
"zip3", "zipWith", "zipWith3");
return wkw;
})();
return {
startState: function () { return { f: normal }; },
copyState: function (s) { return { f: s.f }; },
token: function(stream, state) {
var t = state.f(stream, function(s) { state.f = s; });
var w = stream.current();
return (w in wellKnownWords) ? wellKnownWords[w] : t;
}
};
});
CodeMirror.defineMIME("text/x-haskell", "haskell");

View File

@@ -0,0 +1,60 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Haskell mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="haskell.js"></script>
<link rel="stylesheet" href="../../theme/elegant.css">
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Haskell mode</h1>
<form><textarea id="code" name="code">
module UniquePerms (
uniquePerms
)
where
-- | Find all unique permutations of a list where there might be duplicates.
uniquePerms :: (Eq a) => [a] -> [[a]]
uniquePerms = permBag . makeBag
-- | An unordered collection where duplicate values are allowed,
-- but represented with a single value and a count.
type Bag a = [(a, Int)]
makeBag :: (Eq a) => [a] -> Bag a
makeBag [] = []
makeBag (a:as) = mix a $ makeBag as
where
mix a [] = [(a,1)]
mix a (bn@(b,n):bs) | a == b = (b,n+1):bs
| otherwise = bn : mix a bs
permBag :: Bag a -> [[a]]
permBag [] = [[]]
permBag bs = concatMap (\(f,cs) -> map (f:) $ permBag cs) . oneOfEach $ bs
where
oneOfEach [] = []
oneOfEach (an@(a,n):bs) =
let bs' = if n == 1 then bs else (a,n-1):bs
in (a,bs') : mapSnd (an:) (oneOfEach bs)
apSnd f (a,b) = (a, f b)
mapSnd = map . apSnd
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
theme: "elegant"
});
</script>
<p><strong>MIME types defined:</strong> <code>text/x-haskell</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,68 @@
CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
//config settings
var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
//inner modes
var scriptingMode, htmlMixedMode;
//tokenizer when in html mode
function htmlDispatch(stream, state) {
if (stream.match(scriptStartRegex, false)) {
state.token=scriptingDispatch;
return scriptingMode.token(stream, state.scriptState);
}
else
return htmlMixedMode.token(stream, state.htmlState);
}
//tokenizer when in scripting mode
function scriptingDispatch(stream, state) {
if (stream.match(scriptEndRegex, false)) {
state.token=htmlDispatch;
return htmlMixedMode.token(stream, state.htmlState);
}
else
return scriptingMode.token(stream, state.scriptState);
}
return {
startState: function() {
scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
return {
token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
htmlState : htmlMixedMode.startState(),
scriptState : scriptingMode.startState()
}
},
token: function(stream, state) {
return state.token(stream, state);
},
indent: function(state, textAfter) {
if (state.token == htmlDispatch)
return htmlMixedMode.indent(state.htmlState, textAfter);
else
return scriptingMode.indent(state.scriptState, textAfter);
},
copyState: function(state) {
return {
token : state.token,
htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
}
},
electricChars: "/{}:"
}
});
CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});

View File

@@ -0,0 +1,49 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Html Embedded Scripts mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../xml/xml.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
<script src="../htmlmixed/htmlmixed.js"></script>
<script src="htmlembedded.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Html Embedded Scripts mode</h1>
<form><textarea id="code" name="code">
<%
function hello(who) {
return "Hello " + who;
}
%>
This is an example of EJS (embedded javascript)
<p>The program says <%= hello("world") %>.</p>
<script>
alert("And here is some normal JS code"); // also colored
</script>
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "application/x-ejs",
indentUnit: 4,
indentWithTabs: true,
enterMode: "keep",
tabMode: "shift"
});
</script>
<p>Mode for html embedded scripts like JSP and ASP.NET. Depends on HtmlMixed which in turn depends on
JavaScript, CSS and XML.<br />Other dependancies include those of the scriping language chosen.</p>
<p><strong>MIME types defined:</strong> <code>application/x-aspx</code> (ASP.NET),
<code>application/x-ejs</code> (Embedded Javascript), <code>application/x-jsp</code> (JavaServer Pages)</p>
</body>
</html>

View File

@@ -0,0 +1,83 @@
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var jsMode = CodeMirror.getMode(config, "javascript");
var cssMode = CodeMirror.getMode(config, "css");
function html(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (style == "tag" && stream.current() == ">" && state.htmlState.context) {
if (/^script$/i.test(state.htmlState.context.tagName)) {
state.token = javascript;
state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "javascript";
}
else if (/^style$/i.test(state.htmlState.context.tagName)) {
state.token = css;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "css";
}
}
return style;
}
function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat);
if (close > -1) stream.backUp(cur.length - close);
return style;
}
function javascript(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*script\s*>/,
jsMode.token(stream, state.localState));
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*style\s*>/,
cssMode.token(stream, state.localState));
}
return {
startState: function() {
var state = htmlMode.startState();
return {token: html, localState: null, mode: "html", htmlState: state};
},
copyState: function(state) {
if (state.localState)
var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
return {token: state.token, localState: local, mode: state.mode,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function(stream, state) {
return state.token(stream, state);
},
indent: function(state, textAfter) {
if (state.token == html || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
else if (state.token == javascript)
return jsMode.indent(state.localState, textAfter);
else
return cssMode.indent(state.localState, textAfter);
},
compareStates: function(a, b) {
return htmlMode.compareStates(a.htmlState, b.htmlState);
},
electricChars: "/{}:"
}
});
CodeMirror.defineMIME("text/html", "htmlmixed");

View File

@@ -0,0 +1,51 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: HTML mixed mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../xml/xml.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
<script src="htmlmixed.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style>.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: HTML mixed mode</h1>
<form><textarea id="code" name="code">
<html style="color: green">
<!-- this is a comment -->
<head>
<title>Mixed HTML Example</title>
<style type="text/css">
h1 {font-family: comic sans; color: #f0f;}
div {background: yellow !important;}
body {
max-width: 50em;
margin: 1em 2em 1em 5em;
}
</style>
</head>
<body>
<h1>Mixed HTML Example</h1>
<script>
function jsFunc(arg1, arg2) {
if (arg1 && arg2) document.body.innerHTML = "achoo";
}
</script>
</body>
</html>
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "text/html", tabMode: "indent"});
</script>
<p>The HTML mixed mode depends on the XML, JavaScript, and CSS modes.</p>
<p><strong>MIME types defined:</strong> <code>text/html</code>
(redefined, only takes effect if you load this parser after the
XML parser).</p>
</body>
</html>

View File

@@ -0,0 +1,77 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: JavaScript mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="javascript.js"></script>
<link rel="stylesheet" href="../../doc/docs.css">
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
</head>
<body>
<h1>CodeMirror: JavaScript mode</h1>
<div><textarea id="code" name="code">
// Demo code (the actual new parser character stream implementation)
function StringStream(string) {
this.pos = 0;
this.string = string;
}
StringStream.prototype = {
done: function() {return this.pos >= this.string.length;},
peek: function() {return this.string.charAt(this.pos);},
next: function() {
if (this.pos &lt; this.string.length)
return this.string.charAt(this.pos++);
},
eat: function(match) {
var ch = this.string.charAt(this.pos);
if (typeof match == "string") var ok = ch == match;
else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch);
if (ok) {this.pos++; return ch;}
},
eatWhile: function(match) {
var start = this.pos;
while (this.eat(match));
if (this.pos > start) return this.string.slice(start, this.pos);
},
backUp: function(n) {this.pos -= n;},
column: function() {return this.pos;},
eatSpace: function() {
var start = this.pos;
while (/\s/.test(this.string.charAt(this.pos))) this.pos++;
return this.pos - start;
},
match: function(pattern, consume, caseInsensitive) {
if (typeof pattern == "string") {
function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
if (consume !== false) this.pos += str.length;
return true;
}
}
else {
var match = this.string.slice(this.pos).match(pattern);
if (match &amp;&amp; consume !== false) this.pos += match[0].length;
return match;
}
}
};
</textarea></div>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true
});
</script>
<p>JavaScript mode supports a single configuration
option, <code>json</code>, which will set the mode to expect JSON
data rather than a JavaScript program.</p>
<p><strong>MIME types defined:</strong> <code>text/javascript</code>, <code>application/json</code>.</p>
</body>
</html>

View File

@@ -0,0 +1,360 @@
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
return {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.reAllowed) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
if (!cx.state.context) cx.state.localVars = defaultVars;
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator") return cont(expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function vardef1(type, value) {
if (type == "variable"){register(value); return cont(vardef2);}
return cont();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, forspec2);
if (type == ";") return pass(forspec2);
if (type == "variable") return cont(formaybein);
return pass(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
reAllowed: true,
kwAllowed: true,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
state.kwAllowed = type != '.';
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + 4;
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});

View File

@@ -0,0 +1,37 @@
<!doctype html>
<html>
<head>
<title>CodeMirror: Jinja2 mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="jinja2.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Jinja2 mode</h1>
<form><textarea id="code" name="code">
&lt;html style="color: green"&gt;
&lt;!-- this is a comment --&gt;
&lt;head&gt;
&lt;title&gt;Jinja2 Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;ul&gt;
{# this is a comment #}
{%- for item in li -%}
&lt;li&gt;
{{ item.label }}
&lt;/li&gt;
{% endfor -%}
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;
</textarea></form>
<script>
var editor =
CodeMirror.fromTextArea(document.getElementById("code"), {mode:
{name: "jinja2", htmlMode: true}});
</script>
</body>
</html>

View File

@@ -0,0 +1,51 @@
CodeMirror.defineMode("jinja2", function(config, parserConf) {
var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false",
"loop", "none", "self", "super", "if", "as", "not", "and",
"else", "import", "with", "without", "context", 'extends', 'macro', 'endmacro'];
var blocks = ['block', 'for', 'if', 'import', 'with', 'macro', 'raw', 'call', 'filter'];
// TODO raw should stop highlighting until the endraw
var standalone = ['extends', 'import', 'else', 'elif', 'from', 'include', 'set'];
var special = ['in', 'is', 'true', 'false', 'loop', 'none', 'self', 'super',
'as', 'not', 'and', 'if', 'else', 'without', 'with',
'context', 'ignore', 'missing', 'import'];
// TODO list builtin filters and tests
keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");
function tokenBase (stream, state) {
var ch = stream.next();
if (ch == "{") {
if (ch = stream.eat(/\{|%|#/)) {
stream.eat("-");
state.tokenize = inTag(ch);
return "tag";
}
}
}
function inTag (close) {
if (close == "{") {
close = "}";
}
return function (stream, state) {
var ch = stream.next();
if ((ch == close || (ch == "-" && stream.eat(close)))
&& stream.eat("}")) {
state.tokenize = tokenBase;
return "tag";
}
if (stream.match(keywords)) {
return "keyword";
}
return close == "#" ? "comment" : "string";
};
}
return {
startState: function () {
return {tokenize: tokenBase};
},
token: function (stream, state) {
return state.tokenize(stream, state);
}
};
});

Some files were not shown because too many files have changed in this diff Show More