• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {Source,SourceResolver,sourcePositionToStringKey} from "./source-resolver.js"
6import {SelectionBroker} from "./selection-broker.js"
7import {View} from "./view.js"
8import {MySelection} from "./selection.js"
9import {anyToString,ViewElements} from "./util.js"
10
11export enum CodeMode {
12  MAIN_SOURCE = "main function",
13  INLINED_SOURCE = "inlined function"
14};
15
16export class CodeView extends View {
17  broker: SelectionBroker;
18  source: Source;
19  sourceResolver: SourceResolver;
20  codeMode: CodeMode;
21  sourcePositionToHtmlElement: Map<string, HTMLElement>;
22  showAdditionalInliningPosition: boolean;
23  selectionHandler: SelectionHandler;
24  selection: MySelection;
25
26  createViewElement() {
27    const sourceContainer = document.createElement("div");
28    sourceContainer.classList.add("source-container");
29    return sourceContainer;
30  }
31
32  constructor(parentId, broker, sourceResolver, sourceFunction, codeMode: CodeMode) {
33    super(parentId);
34    let view = this;
35    view.broker = broker;
36    view.source = null;
37    view.sourceResolver = sourceResolver;
38    view.source = sourceFunction;
39    view.codeMode = codeMode;
40    this.sourcePositionToHtmlElement = new Map();
41    this.showAdditionalInliningPosition = false;
42
43    const selectionHandler = {
44      clear: function () {
45        view.selection.clear();
46        view.updateSelection();
47        broker.broadcastClear(this)
48      },
49      select: function (sourcePositions, selected) {
50        const locations = [];
51        for (var sourcePosition of sourcePositions) {
52          locations.push(sourcePosition);
53          sourceResolver.addInliningPositions(sourcePosition, locations);
54        }
55        if (locations.length == 0) return;
56        view.selection.select(locations, selected);
57        view.updateSelection();
58        broker.broadcastSourcePositionSelect(this, locations, selected);
59      },
60      brokeredSourcePositionSelect: function (locations, selected) {
61        const firstSelect = view.selection.isEmpty();
62        for (const location of locations) {
63          const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
64          if (!translated) continue;
65          view.selection.select(translated, selected);
66        }
67        view.updateSelection(firstSelect);
68      },
69      brokeredClear: function () {
70        view.selection.clear();
71        view.updateSelection();
72      },
73    };
74    view.selection = new MySelection(sourcePositionToStringKey);
75    broker.addSourcePositionHandler(selectionHandler);
76    this.selectionHandler = selectionHandler;
77    this.initializeCode();
78  }
79
80  addHtmlElementToSourcePosition(sourcePosition, element) {
81    const key = sourcePositionToStringKey(sourcePosition);
82    if (this.sourcePositionToHtmlElement.has(key)) {
83      console.log("Warning: duplicate source position", sourcePosition);
84    }
85    this.sourcePositionToHtmlElement.set(key, element);
86  }
87
88  getHtmlElementForSourcePosition(sourcePosition) {
89    const key = sourcePositionToStringKey(sourcePosition);
90    return this.sourcePositionToHtmlElement.get(key);
91  }
92
93  updateSelection(scrollIntoView: boolean = false): void {
94    const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
95    for (const [sp, el] of this.sourcePositionToHtmlElement.entries()) {
96      const isSelected = this.selection.isKeySelected(sp);
97      mkVisible.consider(el, isSelected);
98      el.classList.toggle("selected", isSelected);
99    }
100    mkVisible.apply(scrollIntoView);
101  }
102
103  initializeContent(data, rememberedSelection) {
104  }
105
106  getCodeHtmlElementName() {
107    return `source-pre-${this.source.sourceId}`;
108  }
109
110  getCodeHeaderHtmlElementName() {
111    return `source-pre-${this.source.sourceId}-header`;
112  }
113
114  getHtmlCodeLines(): NodeListOf<HTMLElement> {
115    const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
116    return ordereList.childNodes as NodeListOf<HTMLElement>;
117  }
118
119  onSelectLine(lineNumber: number, doClear: boolean) {
120    const key = anyToString(lineNumber);
121    if (doClear) {
122      this.selectionHandler.clear();
123    }
124    const positions = this.sourceResolver.linetoSourcePositions(lineNumber - 1);
125    if (positions !== undefined) {
126      this.selectionHandler.select(positions, undefined);
127    }
128  }
129
130  onSelectSourcePosition(sourcePosition, doClear) {
131    if (doClear) {
132      this.selectionHandler.clear();
133    }
134    this.selectionHandler.select([sourcePosition], undefined);
135  }
136
137  initializeCode() {
138    var view = this;
139    const source = this.source;
140    const sourceText = source.sourceText;
141    if (!sourceText) return;
142    const sourceContainer = view.divNode;
143    if (this.codeMode == CodeMode.MAIN_SOURCE) {
144      sourceContainer.classList.add("main-source");
145    } else {
146      sourceContainer.classList.add("inlined-source");
147    }
148    var codeHeader = document.createElement("div");
149    codeHeader.setAttribute("id", this.getCodeHeaderHtmlElementName());
150    codeHeader.classList.add("code-header");
151    var codeFileFunction = document.createElement("div");
152    codeFileFunction.classList.add("code-file-function");
153    codeFileFunction.innerHTML = `${source.sourceName}:${source.functionName}`;
154    codeHeader.appendChild(codeFileFunction);
155    var codeModeDiv = document.createElement("div");
156    codeModeDiv.classList.add("code-mode");
157    codeModeDiv.innerHTML = `${this.codeMode}`;
158    codeHeader.appendChild(codeModeDiv);
159    const clearDiv = document.createElement("div");
160    clearDiv.style.clear = "both";
161    codeHeader.appendChild(clearDiv);
162    sourceContainer.appendChild(codeHeader);
163    var codePre = document.createElement("pre");
164    codePre.setAttribute("id", this.getCodeHtmlElementName());
165    codePre.classList.add("prettyprint");
166    sourceContainer.appendChild(codePre);
167
168    codeHeader.onclick = function myFunction() {
169      if (codePre.style.display === "none") {
170        codePre.style.display = "block";
171      } else {
172        codePre.style.display = "none";
173      }
174    }
175    if (sourceText != "") {
176      codePre.classList.add("linenums");
177      codePre.textContent = sourceText;
178      try {
179        // Wrap in try to work when offline.
180        PR.prettyPrint(undefined, sourceContainer);
181      } catch (e) {
182        console.log(e);
183      }
184
185      view.divNode.onclick = function (e) {
186        view.selectionHandler.clear();
187      }
188
189      const base: number = source.startPosition;
190      let current = 0;
191      const lineListDiv = this.getHtmlCodeLines();
192      let newlineAdjust = 0;
193      for (let i = 0; i < lineListDiv.length; i++) {
194        // Line numbers are not zero-based.
195        const lineNumber = i + 1;
196        const currentLineElement = lineListDiv[i];
197        currentLineElement.id = "li" + i;
198        currentLineElement.dataset.lineNumber = "" + lineNumber;
199        const spans = currentLineElement.childNodes;
200        for (let j = 0; j < spans.length; ++j) {
201          const currentSpan = spans[j];
202          const pos = base + current;
203          const end = pos + currentSpan.textContent.length;
204          current += currentSpan.textContent.length;
205          this.insertSourcePositions(currentSpan, lineNumber, pos, end, newlineAdjust);
206          newlineAdjust = 0;
207        }
208
209        this.insertLineNumber(currentLineElement, lineNumber);
210
211        while ((current < sourceText.length) &&
212          (sourceText[current] == '\n' || sourceText[current] == '\r')) {
213          ++current;
214          ++newlineAdjust;
215        }
216      }
217    }
218  }
219
220  insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
221    const view = this;
222    const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
223    for (const sourcePosition of sps) {
224      this.sourceResolver.addAnyPositionToLine(lineNumber, sourcePosition);
225      const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.firstChild : currentSpan;
226      const replacementNode = textnode.splitText(Math.max(0, sourcePosition.scriptOffset - pos));
227      const span = document.createElement('span');
228      span.setAttribute("scriptOffset", sourcePosition.scriptOffset);
229      span.classList.add("source-position")
230      const marker = document.createElement('span');
231      marker.classList.add("marker")
232      span.appendChild(marker);
233      const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
234      if (inlining != undefined && view.showAdditionalInliningPosition) {
235        const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
236        const inliningMarker = document.createElement('span');
237        inliningMarker.classList.add("inlining-marker")
238        inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`)
239        span.appendChild(inliningMarker);
240      }
241      span.onclick = function (e) {
242        e.stopPropagation();
243        view.onSelectSourcePosition(sourcePosition, !e.shiftKey)
244      };
245      view.addHtmlElementToSourcePosition(sourcePosition, span);
246      textnode.parentNode.insertBefore(span, replacementNode);
247    }
248  }
249
250  insertLineNumber(lineElement, lineNumber) {
251    const view = this;
252    const lineNumberElement = document.createElement("div");
253    lineNumberElement.classList.add("line-number");
254    lineNumberElement.dataset.lineNumber = lineNumber;
255    lineNumberElement.innerText = lineNumber;
256    lineNumberElement.onclick = function (e) {
257      e.stopPropagation();
258      view.onSelectLine(lineNumber, !e.shiftKey);
259    }
260    lineElement.insertBefore(lineNumberElement, lineElement.firstChild)
261    // Don't add lines to source positions of not in backwardsCompatibility mode.
262    if (this.source.backwardsCompatibility === true) {
263      for (const sourcePosition of this.sourceResolver.linetoSourcePositions(lineNumber - 1)) {
264        view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
265      }
266    }
267  }
268
269  deleteContent() { }
270  detachSelection() { return null; }
271}
272