##// END OF EJS Templates
Loading a file works
Thomas Kluyver -
Show More
@@ -1,614 +1,619 b''
1 1 """A contents manager that uses the local file system for storage."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import base64
7 7 import errno
8 8 import io
9 9 import os
10 10 import shutil
11 11 from contextlib import contextmanager
12 import mimetypes
12 13
13 14 from tornado import web
14 15
15 16 from .manager import ContentsManager
16 17 from IPython import nbformat
17 18 from IPython.utils.io import atomic_writing
18 19 from IPython.utils.path import ensure_dir_exists
19 20 from IPython.utils.traitlets import Unicode, Bool, TraitError
20 21 from IPython.utils.py3compat import getcwd, str_to_unicode
21 22 from IPython.utils import tz
22 23 from IPython.html.utils import is_hidden, to_os_path, to_api_path
23 24
24 25
25 26 class FileContentsManager(ContentsManager):
26 27
27 28 root_dir = Unicode(config=True)
28 29
29 30 def _root_dir_default(self):
30 31 try:
31 32 return self.parent.notebook_dir
32 33 except AttributeError:
33 34 return getcwd()
34 35
35 36 @contextmanager
36 37 def perm_to_403(self, os_path=''):
37 38 """context manager for turning permission errors into 403"""
38 39 try:
39 40 yield
40 41 except OSError as e:
41 42 if e.errno in {errno.EPERM, errno.EACCES}:
42 43 # make 403 error message without root prefix
43 44 # this may not work perfectly on unicode paths on Python 2,
44 45 # but nobody should be doing that anyway.
45 46 if not os_path:
46 47 os_path = str_to_unicode(e.filename or 'unknown file')
47 48 path = to_api_path(os_path, self.root_dir)
48 49 raise web.HTTPError(403, u'Permission denied: %s' % path)
49 50 else:
50 51 raise
51 52
52 53 @contextmanager
53 54 def open(self, os_path, *args, **kwargs):
54 55 """wrapper around io.open that turns permission errors into 403"""
55 56 with self.perm_to_403(os_path):
56 57 with io.open(os_path, *args, **kwargs) as f:
57 58 yield f
58 59
59 60 @contextmanager
60 61 def atomic_writing(self, os_path, *args, **kwargs):
61 62 """wrapper around atomic_writing that turns permission errors into 403"""
62 63 with self.perm_to_403(os_path):
63 64 with atomic_writing(os_path, *args, **kwargs) as f:
64 65 yield f
65 66
66 67 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
67 68 def _save_script_changed(self):
68 69 self.log.warn("""
69 70 Automatically saving notebooks as scripts has been removed.
70 71 Use `ipython nbconvert --to python [notebook]` instead.
71 72 """)
72 73
73 74 def _root_dir_changed(self, name, old, new):
74 75 """Do a bit of validation of the root_dir."""
75 76 if not os.path.isabs(new):
76 77 # If we receive a non-absolute path, make it absolute.
77 78 self.root_dir = os.path.abspath(new)
78 79 return
79 80 if not os.path.isdir(new):
80 81 raise TraitError("%r is not a directory" % new)
81 82
82 83 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
83 84 help="""The directory name in which to keep file checkpoints
84 85
85 86 This is a path relative to the file's own directory.
86 87
87 88 By default, it is .ipynb_checkpoints
88 89 """
89 90 )
90 91
91 92 def _copy(self, src, dest):
92 93 """copy src to dest
93 94
94 95 like shutil.copy2, but log errors in copystat
95 96 """
96 97 shutil.copyfile(src, dest)
97 98 try:
98 99 shutil.copystat(src, dest)
99 100 except OSError as e:
100 101 self.log.debug("copystat on %s failed", dest, exc_info=True)
101 102
102 103 def _get_os_path(self, path):
103 104 """Given an API path, return its file system path.
104 105
105 106 Parameters
106 107 ----------
107 108 path : string
108 109 The relative API path to the named file.
109 110
110 111 Returns
111 112 -------
112 113 path : string
113 114 Native, absolute OS path to for a file.
114 115 """
115 116 return to_os_path(path, self.root_dir)
116 117
117 118 def dir_exists(self, path):
118 119 """Does the API-style path refer to an extant directory?
119 120
120 121 API-style wrapper for os.path.isdir
121 122
122 123 Parameters
123 124 ----------
124 125 path : string
125 126 The path to check. This is an API path (`/` separated,
126 127 relative to root_dir).
127 128
128 129 Returns
129 130 -------
130 131 exists : bool
131 132 Whether the path is indeed a directory.
132 133 """
133 134 path = path.strip('/')
134 135 os_path = self._get_os_path(path=path)
135 136 return os.path.isdir(os_path)
136 137
137 138 def is_hidden(self, path):
138 139 """Does the API style path correspond to a hidden directory or file?
139 140
140 141 Parameters
141 142 ----------
142 143 path : string
143 144 The path to check. This is an API path (`/` separated,
144 145 relative to root_dir).
145 146
146 147 Returns
147 148 -------
148 149 hidden : bool
149 150 Whether the path exists and is hidden.
150 151 """
151 152 path = path.strip('/')
152 153 os_path = self._get_os_path(path=path)
153 154 return is_hidden(os_path, self.root_dir)
154 155
155 156 def file_exists(self, path):
156 157 """Returns True if the file exists, else returns False.
157 158
158 159 API-style wrapper for os.path.isfile
159 160
160 161 Parameters
161 162 ----------
162 163 path : string
163 164 The relative path to the file (with '/' as separator)
164 165
165 166 Returns
166 167 -------
167 168 exists : bool
168 169 Whether the file exists.
169 170 """
170 171 path = path.strip('/')
171 172 os_path = self._get_os_path(path)
172 173 return os.path.isfile(os_path)
173 174
174 175 def exists(self, path):
175 176 """Returns True if the path exists, else returns False.
176 177
177 178 API-style wrapper for os.path.exists
178 179
179 180 Parameters
180 181 ----------
181 182 path : string
182 183 The API path to the file (with '/' as separator)
183 184
184 185 Returns
185 186 -------
186 187 exists : bool
187 188 Whether the target exists.
188 189 """
189 190 path = path.strip('/')
190 191 os_path = self._get_os_path(path=path)
191 192 return os.path.exists(os_path)
192 193
193 194 def _base_model(self, path):
194 195 """Build the common base of a contents model"""
195 196 os_path = self._get_os_path(path)
196 197 info = os.stat(os_path)
197 198 last_modified = tz.utcfromtimestamp(info.st_mtime)
198 199 created = tz.utcfromtimestamp(info.st_ctime)
199 200 # Create the base model.
200 201 model = {}
201 202 model['name'] = path.rsplit('/', 1)[-1]
202 203 model['path'] = path
203 204 model['last_modified'] = last_modified
204 205 model['created'] = created
205 206 model['content'] = None
206 207 model['format'] = None
208 model['mimetype'] = None
207 209 try:
208 210 model['writable'] = os.access(os_path, os.W_OK)
209 211 except OSError:
210 212 self.log.error("Failed to check write permissions on %s", os_path)
211 213 model['writable'] = False
212 214 return model
213 215
214 216 def _dir_model(self, path, content=True):
215 217 """Build a model for a directory
216 218
217 219 if content is requested, will include a listing of the directory
218 220 """
219 221 os_path = self._get_os_path(path)
220 222
221 223 four_o_four = u'directory does not exist: %r' % path
222 224
223 225 if not os.path.isdir(os_path):
224 226 raise web.HTTPError(404, four_o_four)
225 227 elif is_hidden(os_path, self.root_dir):
226 228 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
227 229 os_path
228 230 )
229 231 raise web.HTTPError(404, four_o_four)
230 232
231 233 model = self._base_model(path)
232 234 model['type'] = 'directory'
233 235 if content:
234 236 model['content'] = contents = []
235 237 os_dir = self._get_os_path(path)
236 238 for name in os.listdir(os_dir):
237 239 os_path = os.path.join(os_dir, name)
238 240 # skip over broken symlinks in listing
239 241 if not os.path.exists(os_path):
240 242 self.log.warn("%s doesn't exist", os_path)
241 243 continue
242 244 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
243 245 self.log.debug("%s not a regular file", os_path)
244 246 continue
245 247 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
246 248 contents.append(self.get(
247 249 path='%s/%s' % (path, name),
248 250 content=False)
249 251 )
250 252
251 253 model['format'] = 'json'
252 254
253 255 return model
254 256
255 257 def _file_model(self, path, content=True, format=None):
256 258 """Build a model for a file
257 259
258 260 if content is requested, include the file contents.
259 261
260 262 format:
261 263 If 'text', the contents will be decoded as UTF-8.
262 264 If 'base64', the raw bytes contents will be encoded as base64.
263 265 If not specified, try to decode as UTF-8, and fall back to base64
264 266 """
265 267 model = self._base_model(path)
266 268 model['type'] = 'file'
269
270 os_path = self._get_os_path(path)
271 model['mimetype'] = mimetypes.guess_type(os_path)[0] or 'text/plain'
272
267 273 if content:
268 os_path = self._get_os_path(path)
269 274 if not os.path.isfile(os_path):
270 275 # could be FIFO
271 276 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
272 277 with self.open(os_path, 'rb') as f:
273 278 bcontent = f.read()
274 279
275 280 if format != 'base64':
276 281 try:
277 282 model['content'] = bcontent.decode('utf8')
278 283 except UnicodeError as e:
279 284 if format == 'text':
280 285 raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
281 286 else:
282 287 model['format'] = 'text'
283 288
284 289 if model['content'] is None:
285 290 model['content'] = base64.encodestring(bcontent).decode('ascii')
286 291 model['format'] = 'base64'
287 292
288 293 return model
289 294
290 295
291 296 def _notebook_model(self, path, content=True):
292 297 """Build a notebook model
293 298
294 299 if content is requested, the notebook content will be populated
295 300 as a JSON structure (not double-serialized)
296 301 """
297 302 model = self._base_model(path)
298 303 model['type'] = 'notebook'
299 304 if content:
300 305 os_path = self._get_os_path(path)
301 306 with self.open(os_path, 'r', encoding='utf-8') as f:
302 307 try:
303 308 nb = nbformat.read(f, as_version=4)
304 309 except Exception as e:
305 310 raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
306 311 self.mark_trusted_cells(nb, path)
307 312 model['content'] = nb
308 313 model['format'] = 'json'
309 314 self.validate_notebook_model(model)
310 315 return model
311 316
312 317 def get(self, path, content=True, type_=None, format=None):
313 318 """ Takes a path for an entity and returns its model
314 319
315 320 Parameters
316 321 ----------
317 322 path : str
318 323 the API path that describes the relative path for the target
319 324 content : bool
320 325 Whether to include the contents in the reply
321 326 type_ : str, optional
322 327 The requested type - 'file', 'notebook', or 'directory'.
323 328 Will raise HTTPError 400 if the content doesn't match.
324 329 format : str, optional
325 330 The requested format for file contents. 'text' or 'base64'.
326 331 Ignored if this returns a notebook or directory model.
327 332
328 333 Returns
329 334 -------
330 335 model : dict
331 336 the contents model. If content=True, returns the contents
332 337 of the file or directory as well.
333 338 """
334 339 path = path.strip('/')
335 340
336 341 if not self.exists(path):
337 342 raise web.HTTPError(404, u'No such file or directory: %s' % path)
338 343
339 344 os_path = self._get_os_path(path)
340 345 if os.path.isdir(os_path):
341 346 if type_ not in (None, 'directory'):
342 347 raise web.HTTPError(400,
343 348 u'%s is a directory, not a %s' % (path, type_))
344 349 model = self._dir_model(path, content=content)
345 350 elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
346 351 model = self._notebook_model(path, content=content)
347 352 else:
348 353 if type_ == 'directory':
349 354 raise web.HTTPError(400,
350 355 u'%s is not a directory')
351 356 model = self._file_model(path, content=content, format=format)
352 357 return model
353 358
354 359 def _save_notebook(self, os_path, model, path=''):
355 360 """save a notebook file"""
356 361 # Save the notebook file
357 362 nb = nbformat.from_dict(model['content'])
358 363
359 364 self.check_and_sign(nb, path)
360 365
361 366 with self.atomic_writing(os_path, encoding='utf-8') as f:
362 367 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
363 368
364 369 def _save_file(self, os_path, model, path=''):
365 370 """save a non-notebook file"""
366 371 fmt = model.get('format', None)
367 372 if fmt not in {'text', 'base64'}:
368 373 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
369 374 try:
370 375 content = model['content']
371 376 if fmt == 'text':
372 377 bcontent = content.encode('utf8')
373 378 else:
374 379 b64_bytes = content.encode('ascii')
375 380 bcontent = base64.decodestring(b64_bytes)
376 381 except Exception as e:
377 382 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
378 383 with self.atomic_writing(os_path, text=False) as f:
379 384 f.write(bcontent)
380 385
381 386 def _save_directory(self, os_path, model, path=''):
382 387 """create a directory"""
383 388 if is_hidden(os_path, self.root_dir):
384 389 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
385 390 if not os.path.exists(os_path):
386 391 with self.perm_to_403():
387 392 os.mkdir(os_path)
388 393 elif not os.path.isdir(os_path):
389 394 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
390 395 else:
391 396 self.log.debug("Directory %r already exists", os_path)
392 397
393 398 def save(self, model, path=''):
394 399 """Save the file model and return the model with no content."""
395 400 path = path.strip('/')
396 401
397 402 if 'type' not in model:
398 403 raise web.HTTPError(400, u'No file type provided')
399 404 if 'content' not in model and model['type'] != 'directory':
400 405 raise web.HTTPError(400, u'No file content provided')
401 406
402 407 # One checkpoint should always exist
403 408 if self.file_exists(path) and not self.list_checkpoints(path):
404 409 self.create_checkpoint(path)
405 410
406 411 os_path = self._get_os_path(path)
407 412 self.log.debug("Saving %s", os_path)
408 413 try:
409 414 if model['type'] == 'notebook':
410 415 self._save_notebook(os_path, model, path)
411 416 elif model['type'] == 'file':
412 417 self._save_file(os_path, model, path)
413 418 elif model['type'] == 'directory':
414 419 self._save_directory(os_path, model, path)
415 420 else:
416 421 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
417 422 except web.HTTPError:
418 423 raise
419 424 except Exception as e:
420 425 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
421 426 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
422 427
423 428 validation_message = None
424 429 if model['type'] == 'notebook':
425 430 self.validate_notebook_model(model)
426 431 validation_message = model.get('message', None)
427 432
428 433 model = self.get(path, content=False)
429 434 if validation_message:
430 435 model['message'] = validation_message
431 436 return model
432 437
433 438 def update(self, model, path):
434 439 """Update the file's path
435 440
436 441 For use in PATCH requests, to enable renaming a file without
437 442 re-uploading its contents. Only used for renaming at the moment.
438 443 """
439 444 path = path.strip('/')
440 445 new_path = model.get('path', path).strip('/')
441 446 if path != new_path:
442 447 self.rename(path, new_path)
443 448 model = self.get(new_path, content=False)
444 449 return model
445 450
446 451 def delete(self, path):
447 452 """Delete file at path."""
448 453 path = path.strip('/')
449 454 os_path = self._get_os_path(path)
450 455 rm = os.unlink
451 456 if os.path.isdir(os_path):
452 457 listing = os.listdir(os_path)
453 458 # don't delete non-empty directories (checkpoints dir doesn't count)
454 459 if listing and listing != [self.checkpoint_dir]:
455 460 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
456 461 elif not os.path.isfile(os_path):
457 462 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
458 463
459 464 # clear checkpoints
460 465 for checkpoint in self.list_checkpoints(path):
461 466 checkpoint_id = checkpoint['id']
462 467 cp_path = self.get_checkpoint_path(checkpoint_id, path)
463 468 if os.path.isfile(cp_path):
464 469 self.log.debug("Unlinking checkpoint %s", cp_path)
465 470 with self.perm_to_403():
466 471 rm(cp_path)
467 472
468 473 if os.path.isdir(os_path):
469 474 self.log.debug("Removing directory %s", os_path)
470 475 with self.perm_to_403():
471 476 shutil.rmtree(os_path)
472 477 else:
473 478 self.log.debug("Unlinking file %s", os_path)
474 479 with self.perm_to_403():
475 480 rm(os_path)
476 481
477 482 def rename(self, old_path, new_path):
478 483 """Rename a file."""
479 484 old_path = old_path.strip('/')
480 485 new_path = new_path.strip('/')
481 486 if new_path == old_path:
482 487 return
483 488
484 489 new_os_path = self._get_os_path(new_path)
485 490 old_os_path = self._get_os_path(old_path)
486 491
487 492 # Should we proceed with the move?
488 493 if os.path.exists(new_os_path):
489 494 raise web.HTTPError(409, u'File already exists: %s' % new_path)
490 495
491 496 # Move the file
492 497 try:
493 498 with self.perm_to_403():
494 499 shutil.move(old_os_path, new_os_path)
495 500 except web.HTTPError:
496 501 raise
497 502 except Exception as e:
498 503 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
499 504
500 505 # Move the checkpoints
501 506 old_checkpoints = self.list_checkpoints(old_path)
502 507 for cp in old_checkpoints:
503 508 checkpoint_id = cp['id']
504 509 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path)
505 510 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path)
506 511 if os.path.isfile(old_cp_path):
507 512 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
508 513 with self.perm_to_403():
509 514 shutil.move(old_cp_path, new_cp_path)
510 515
511 516 # Checkpoint-related utilities
512 517
513 518 def get_checkpoint_path(self, checkpoint_id, path):
514 519 """find the path to a checkpoint"""
515 520 path = path.strip('/')
516 521 parent, name = ('/' + path).rsplit('/', 1)
517 522 parent = parent.strip('/')
518 523 basename, ext = os.path.splitext(name)
519 524 filename = u"{name}-{checkpoint_id}{ext}".format(
520 525 name=basename,
521 526 checkpoint_id=checkpoint_id,
522 527 ext=ext,
523 528 )
524 529 os_path = self._get_os_path(path=parent)
525 530 cp_dir = os.path.join(os_path, self.checkpoint_dir)
526 531 with self.perm_to_403():
527 532 ensure_dir_exists(cp_dir)
528 533 cp_path = os.path.join(cp_dir, filename)
529 534 return cp_path
530 535
531 536 def get_checkpoint_model(self, checkpoint_id, path):
532 537 """construct the info dict for a given checkpoint"""
533 538 path = path.strip('/')
534 539 cp_path = self.get_checkpoint_path(checkpoint_id, path)
535 540 stats = os.stat(cp_path)
536 541 last_modified = tz.utcfromtimestamp(stats.st_mtime)
537 542 info = dict(
538 543 id = checkpoint_id,
539 544 last_modified = last_modified,
540 545 )
541 546 return info
542 547
543 548 # public checkpoint API
544 549
545 550 def create_checkpoint(self, path):
546 551 """Create a checkpoint from the current state of a file"""
547 552 path = path.strip('/')
548 553 if not self.file_exists(path):
549 554 raise web.HTTPError(404)
550 555 src_path = self._get_os_path(path)
551 556 # only the one checkpoint ID:
552 557 checkpoint_id = u"checkpoint"
553 558 cp_path = self.get_checkpoint_path(checkpoint_id, path)
554 559 self.log.debug("creating checkpoint for %s", path)
555 560 with self.perm_to_403():
556 561 self._copy(src_path, cp_path)
557 562
558 563 # return the checkpoint info
559 564 return self.get_checkpoint_model(checkpoint_id, path)
560 565
561 566 def list_checkpoints(self, path):
562 567 """list the checkpoints for a given file
563 568
564 569 This contents manager currently only supports one checkpoint per file.
565 570 """
566 571 path = path.strip('/')
567 572 checkpoint_id = "checkpoint"
568 573 os_path = self.get_checkpoint_path(checkpoint_id, path)
569 574 if not os.path.exists(os_path):
570 575 return []
571 576 else:
572 577 return [self.get_checkpoint_model(checkpoint_id, path)]
573 578
574 579
575 580 def restore_checkpoint(self, checkpoint_id, path):
576 581 """restore a file to a checkpointed state"""
577 582 path = path.strip('/')
578 583 self.log.info("restoring %s from checkpoint %s", path, checkpoint_id)
579 584 nb_path = self._get_os_path(path)
580 585 cp_path = self.get_checkpoint_path(checkpoint_id, path)
581 586 if not os.path.isfile(cp_path):
582 587 self.log.debug("checkpoint file does not exist: %s", cp_path)
583 588 raise web.HTTPError(404,
584 589 u'checkpoint does not exist: %s@%s' % (path, checkpoint_id)
585 590 )
586 591 # ensure notebook is readable (never restore from an unreadable notebook)
587 592 if cp_path.endswith('.ipynb'):
588 593 with self.open(cp_path, 'r', encoding='utf-8') as f:
589 594 nbformat.read(f, as_version=4)
590 595 self.log.debug("copying %s -> %s", cp_path, nb_path)
591 596 with self.perm_to_403():
592 597 self._copy(cp_path, nb_path)
593 598
594 599 def delete_checkpoint(self, checkpoint_id, path):
595 600 """delete a file's checkpoint"""
596 601 path = path.strip('/')
597 602 cp_path = self.get_checkpoint_path(checkpoint_id, path)
598 603 if not os.path.isfile(cp_path):
599 604 raise web.HTTPError(404,
600 605 u'Checkpoint does not exist: %s@%s' % (path, checkpoint_id)
601 606 )
602 607 self.log.debug("unlinking %s", cp_path)
603 608 os.unlink(cp_path)
604 609
605 610 def info_string(self):
606 611 return "Serving notebooks from local directory: %s" % self.root_dir
607 612
608 613 def get_kernel_path(self, path, model=None):
609 614 """Return the initial working dir a kernel associated with a given notebook"""
610 615 if '/' in path:
611 616 parent_dir = path.rsplit('/', 1)[0]
612 617 else:
613 618 parent_dir = ''
614 619 return self._get_os_path(parent_dir)
@@ -1,23 +1,60 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 require([
5 5 'jquery',
6 6 'base/js/utils',
7 7 'base/js/page',
8 'contents',
8 9 'codemirror/lib/codemirror',
10 'codemirror/mode/meta',
9 11 'custom/custom',
10 12 ], function(
11 13 $,
12 14 utils,
13 15 page,
16 contents,
14 17 CodeMirror
15 18 ){
16 19 page = new page.Page();
17 20
18 21 var base_url = utils.get_body_data('baseUrl');
19 var cm_instance = CodeMirror($('#texteditor-container')[0]);
20
21 page.show();
22 contents = new contents.Contents({base_url: base_url});
22 23
24 var file_path = utils.get_body_data('filePath');
25 var ix = file_path.lastIndexOf("/");
26 var dir_path, basename;
27 if (ix == -1) {
28 dir_path = '';
29 basename = file_path;
30 } else {
31 dir_path = file_path.substring(0, ix);
32 basename = file_path.substring(ix);
33 }
34 contents.load(dir_path, basename, {
35 success: function(model) {
36 page.show();
37 if (model.type === "file" && model.format === "text") {
38 console.log(modeinfo);
39 var cm = CodeMirror($('#texteditor-container')[0], {
40 value: model.content,
41 });
42
43 // Find and load the highlighting mode
44 var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
45 if (modeinfo) {
46 utils.requireCodeMirrorMode(modeinfo.mode, function() {
47 cm.setOption('mode', modeinfo.mode);
48 });
49 }
50
51 // Attach to document for debugging
52 document.cm_instance = cm;
53 } else {
54 $('#texteditor-container').append(
55 $('<p/>').text(dir_path + " is not a text file")
56 );
57 }
58 }
59 });
23 60 });
@@ -1,23 +1,29 b''
1 1 {% extends "page.html" %}
2 2
3 3 {% block title %}{{page_title}}{% endblock %}
4 4
5 {% block stylesheet %}
6 <link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
7
8 {{super()}}
9 {% endblock %}
10
5 11 {% block params %}
6 12
7 13 data-base-url="{{base_url}}"
14 data-file-path="{{file_path}}"
8 15
9 16 {% endblock %}
10 17
11
12 18 {% block site %}
13 19
14 20 <div id="texteditor-container"></div>
15 21
16 22 {% endblock %}
17 23
18 24 {% block script %}
19 25
20 26 {{super()}}
21 27
22 28 <script src="{{ static_url("texteditor/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
23 29 {% endblock %}
@@ -1,18 +1,27 b''
1 1 #encoding: utf-8
2 2 """Tornado handlers for the terminal emulator."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from tornado import web
8 8 from ..base.handlers import IPythonHandler, file_path_regex
9 from ..utils import url_escape
9 10
10 11 class EditorHandler(IPythonHandler):
11 12 """Render the terminal interface."""
12 13 @web.authenticated
13 14 def get(self, path, name):
14 self.write(self.render_template('texteditor.html'))
15 path = path.strip('/')
16 if not self.contents_manager.file_exists(name, path):
17 raise web.HTTPError(404, u'File does not exist: %s/%s' % (path, name))
18
19 file_path = url_escape(path) + "/" + url_escape(name)
20 self.write(self.render_template('texteditor.html',
21 file_path=file_path,
22 )
23 )
15 24
16 25 default_handlers = [
17 26 (r"/texteditor%s" % file_path_regex, EditorHandler),
18 27 ] No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now