default.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. 'use strict';
  4. import * as CodeMirror
  5. from 'codemirror';
  6. import {
  7. IKernelSpecId, IContentsOpts, IKernelId
  8. } from 'jupyter-js-services';
  9. import {
  10. ISignal, Signal
  11. } from 'phosphor-signaling';
  12. import {
  13. Widget
  14. } from 'phosphor-widget';
  15. import {
  16. loadModeByFileName
  17. } from '../codemirror';
  18. import {
  19. CodeMirrorWidget
  20. } from '../codemirror/widget';
  21. import {
  22. IDocumentModel, IWidgetFactory, IModelFactory, IDocumentContext,
  23. IKernelPreference
  24. } from './index';
  25. /**
  26. * The class name added to a dirty widget.
  27. */
  28. const DIRTY_CLASS = 'jp-mod-dirty';
  29. /**
  30. * The class name added to a jupyter code mirror widget.
  31. */
  32. const EDITOR_CLASS = 'jp-CodeMirrorWidget';
  33. /**
  34. * The default implementation of a document model.
  35. */
  36. export
  37. class DocumentModel implements IDocumentModel {
  38. /**
  39. * Construct a new document model.
  40. */
  41. constructor(languagePreference: string) {
  42. this._defaultLang = languagePreference;
  43. }
  44. /**
  45. * Get whether the model factory has been disposed.
  46. */
  47. get isDisposed(): boolean {
  48. return this._isDisposed;
  49. }
  50. /**
  51. * Dispose of the resources held by the document manager.
  52. */
  53. dispose(): void {
  54. this._isDisposed = true;
  55. }
  56. /**
  57. * A signal emitted when the document content changes.
  58. */
  59. get contentChanged(): ISignal<IDocumentModel, string> {
  60. return Private.contentChangedSignal.bind(this);
  61. }
  62. /**
  63. * A signal emitted when the document dirty state changes.
  64. */
  65. get dirtyChanged(): ISignal<IDocumentModel, boolean> {
  66. return Private.dirtyChangedSignal.bind(this);
  67. }
  68. /**
  69. * The dirty state of the document.
  70. */
  71. get dirty(): boolean {
  72. return this._dirty;
  73. }
  74. set dirty(value: boolean) {
  75. if (value === this._dirty) {
  76. return;
  77. }
  78. this._dirty = value;
  79. this.dirtyChanged.emit(value);
  80. }
  81. /**
  82. * The read only state of the document.
  83. */
  84. get readOnly(): boolean {
  85. return this._readOnly;
  86. }
  87. set readOnly(value: boolean) {
  88. if (value === this._readOnly) {
  89. return;
  90. }
  91. this._readOnly = value;
  92. }
  93. /**
  94. * The default kernel name of the document.
  95. *
  96. * #### Notes
  97. * This is a read-only property.
  98. */
  99. get defaultKernelName(): string {
  100. return '';
  101. }
  102. /**
  103. * The default kernel language of the document.
  104. *
  105. * #### Notes
  106. * This is a read-only property.
  107. */
  108. get defaultKernelLanguage(): string {
  109. return this._defaultLang;
  110. }
  111. /**
  112. * Serialize the model to a string.
  113. */
  114. toString(): string {
  115. return this._text;
  116. }
  117. /**
  118. * Deserialize the model from a string.
  119. *
  120. * #### Notes
  121. * Should emit a [contentChanged] signal.
  122. */
  123. fromString(value: string): void {
  124. if (this._text === value) {
  125. return;
  126. }
  127. this._text = value;
  128. this.contentChanged.emit(value);
  129. this.dirty = true;
  130. }
  131. /**
  132. * Serialize the model to JSON.
  133. */
  134. toJSON(): any {
  135. return JSON.stringify(this._text);
  136. }
  137. /**
  138. * Deserialize the model from JSON.
  139. *
  140. * #### Notes
  141. * Should emit a [contentChanged] signal.
  142. */
  143. fromJSON(value: any): void {
  144. this.fromString(JSON.parse(value));
  145. }
  146. private _text = '';
  147. private _defaultLang = '';
  148. private _dirty = false;
  149. private _readOnly = false;
  150. private _isDisposed = false;
  151. }
  152. /**
  153. * The default implementation of a model factory.
  154. */
  155. export
  156. class ModelFactory {
  157. /**
  158. * Get whether the model factory has been disposed.
  159. */
  160. get isDisposed(): boolean {
  161. return this._isDisposed;
  162. }
  163. /**
  164. * Dispose of the resources held by the document manager.
  165. */
  166. dispose(): void {
  167. this._isDisposed = true;
  168. }
  169. /**
  170. * Create a new model for a given path.
  171. *
  172. * @param languagePreference - An optional kernel language preference.
  173. *
  174. * @returns A new document model.
  175. */
  176. createNew(languagePreference?: string): IDocumentModel {
  177. return new DocumentModel(languagePreference);
  178. }
  179. /**
  180. * Get the preferred kernel language given a path.
  181. */
  182. preferredLanguage(path: string): string {
  183. // TODO: use a mapping of extension to language.
  184. return '';
  185. }
  186. private _isDisposed = false;
  187. }
  188. /**
  189. * A document widget for codemirrors.
  190. */
  191. export
  192. class EditorWidget extends CodeMirrorWidget {
  193. /**
  194. * Construct a new editor widget.
  195. */
  196. constructor(model: IDocumentModel, context: IDocumentContext) {
  197. super();
  198. this.addClass(EDITOR_CLASS);
  199. let editor = this.editor;
  200. let doc = editor.getDoc();
  201. doc.setValue(model.toString());
  202. this.title.text = context.path.split('/').pop();
  203. loadModeByFileName(editor, context.path);
  204. model.dirtyChanged.connect((m, value) => {
  205. if (value) {
  206. this.title.className += ` ${DIRTY_CLASS}`;
  207. } else {
  208. this.title.className = this.title.className.replace(DIRTY_CLASS, '');
  209. }
  210. });
  211. context.pathChanged.connect((c, path) => {
  212. loadModeByFileName(editor, path);
  213. this.title.text = path.split('/').pop();
  214. });
  215. model.contentChanged.connect((m, text) => {
  216. let old = doc.getValue();
  217. if (old !== text) {
  218. doc.setValue(text);
  219. }
  220. });
  221. CodeMirror.on(doc, 'change', (instance, change) => {
  222. if (change.origin !== 'setValue') {
  223. model.fromString(instance.getValue());
  224. }
  225. });
  226. }
  227. }
  228. /**
  229. * The default implemetation of a widget factory.
  230. */
  231. export
  232. class WidgetFactory implements IWidgetFactory<EditorWidget> {
  233. /**
  234. * Get whether the model factory has been disposed.
  235. */
  236. get isDisposed(): boolean {
  237. return this._isDisposed;
  238. }
  239. /**
  240. * Dispose of the resources held by the document manager.
  241. */
  242. dispose(): void {
  243. this._isDisposed = true;
  244. }
  245. /**
  246. * Create a new widget given a document model and a context.
  247. */
  248. createNew(model: IDocumentModel, context: IDocumentContext, kernel: IKernelId): EditorWidget {
  249. // TODO: if a kernel id or a name other than 'none' or 'default'
  250. // was given, start that kernel
  251. return new EditorWidget(model, context);
  252. }
  253. /**
  254. * Take an action on a widget before closing it.
  255. *
  256. * @returns A promise that resolves to true if the document should close
  257. * and false otherwise.
  258. */
  259. beforeClose(model: IDocumentModel, context: IDocumentContext, widget: Widget): Promise<boolean> {
  260. // TODO: handle live kernels here.
  261. return Promise.resolve(true);
  262. }
  263. private _isDisposed = false;
  264. }
  265. /**
  266. * A private namespace for data.
  267. */
  268. namespace Private {
  269. /**
  270. * A signal emitted when a document content changes.
  271. */
  272. export
  273. const contentChangedSignal = new Signal<IDocumentModel, string>();
  274. /**
  275. * A signal emitted when a document dirty state changes.
  276. */
  277. export
  278. const dirtyChangedSignal = new Signal<IDocumentModel, boolean>();
  279. }