sqlui 0.1.38 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5df014dd6a9df573d3d336f9d06e424ecb522ddcd9e34dcd1f2ecb4feeb97b00
4
- data.tar.gz: 923e0415e6d274072c6aafbb0a202ea1c319f7c0a61e624b097bff009f68be1f
3
+ metadata.gz: 473ef1be7a3ad0a4e28d2ebf7ab4f52bfcb140a24ccbfcabab5ccbccf7e5d699
4
+ data.tar.gz: 8236b7ddf4245a64738b304029a291a04c33d7079045b3456d61b22d1f1fcf1c
5
5
  SHA512:
6
- metadata.gz: fd163f02e3db9e4d42cdf123394218cc02705a91ea9b02048cf79bfaf83fbb423f2ed61edd758280068aa3660fc4b9af1f474251d57569840e768f54c41e9aa9
7
- data.tar.gz: 4d61710e2e5795ae772d42fcc814f7643b2935a8fa5223258b400ef7ae23c1a5b8b2363bada7ecf9bf28a36494f8db818e27aab97bd1619e48865dab5ce24c0b
6
+ metadata.gz: 38dd32e339981bba7a401a6c53f5cf8233ffbe8d98fc69f79ceb4c575adbe8b7a2d7e6c7ab97f9a835ae69f63423b8b925978565c5d6e66aa5a3efa71e937e5a
7
+ data.tar.gz: 4e56ef90eba0ce8ad231fb80f7c93cb740eab46208a0c8a27d16e3f34568f4b40ece0c68d92f0c81101ad784e78609f34c90158731ef61d72ee3952847c6a2e1
data/.version CHANGED
@@ -1 +1 @@
1
- 0.1.38
1
+ 0.1.39
data/app/args.rb CHANGED
@@ -20,13 +20,21 @@ class Args
20
20
  value
21
21
  end
22
22
 
23
+ def self.fetch_optional_hash(hash, key)
24
+ fetch_optional(hash, key, Hash)
25
+ end
26
+
23
27
  def self.fetch_non_nil(hash, key, *classes)
24
28
  raise ArgumentError, "required parameter #{key} missing" unless hash.key?(key)
25
29
 
26
- value = hash[key]
27
- raise ArgumentError, "required parameter #{key} null" if value.nil?
30
+ raise ArgumentError, "required parameter #{key} null" if hash[key].nil?
28
31
 
29
- if classes.size.positive? && !classes.find { |clazz| value.is_a?(clazz) }
32
+ fetch_optional(hash, key, *classes)
33
+ end
34
+
35
+ def self.fetch_optional(hash, key, *classes)
36
+ value = hash[key]
37
+ if value && classes.size.positive? && !classes.find { |clazz| value.is_a?(clazz) }
30
38
  if classes.size != 1
31
39
  raise ArgumentError, "required parameter #{key} not #{classes.map(&:to_s).map(&:downcase).join(' or ')}"
32
40
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'mysql2'
4
+ require 'set'
5
+
4
6
  require_relative 'args'
5
7
 
6
8
  # Config for a single database.
7
9
  class DatabaseConfig
8
- attr_reader :display_name, :description, :url_path, :saved_path, :client_params
10
+ attr_reader :display_name, :description, :url_path, :saved_path, :table_aliases, :client_params
9
11
 
10
12
  def initialize(hash)
11
13
  @display_name = Args.fetch_non_empty_string(hash, :display_name).strip
@@ -15,6 +17,15 @@ class DatabaseConfig
15
17
  raise ArgumentError, 'url_path should not end with a /' if @url_path.length > 1 && @url_path.end_with?('/')
16
18
 
17
19
  @saved_path = Args.fetch_non_empty_string(hash, :saved_path).strip
20
+ @table_aliases = Args.fetch_optional_hash(hash, :table_aliases) || {}
21
+ @table_aliases = @table_aliases.each do |table, a|
22
+ raise ArgumentError, "invalid alias for table #{table} (#{a}), expected string" unless a.is_a?(String)
23
+ end
24
+ duplicate_aliases = @table_aliases.reject { |(_, v)| @table_aliases.values.count(v) == 1 }.to_h.values.to_set
25
+ if @table_aliases.values.to_set.size < @table_aliases.values.size
26
+ raise ArgumentError, "duplicate table aliases: #{duplicate_aliases.join(', ')}"
27
+ end
28
+
18
29
  @client_params = Args.fetch_non_empty_hash(hash, :client_params)
19
30
  end
20
31
 
data/app/server.rb CHANGED
@@ -57,6 +57,7 @@ class Server < Sinatra::Base
57
57
  server: "#{config.name} - #{database.display_name}",
58
58
  list_url_path: config.list_url_path,
59
59
  schemas: DatabaseMetadata.lookup(client, database),
60
+ table_aliases: database.table_aliases,
60
61
  saved: Dir.glob("#{database.saved_path}/*.sql").to_h do |path|
61
62
  contents = File.read(path)
62
63
  comment_lines = contents.split("\n").take_while do |l|
data/app/sqlui_config.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
+ require 'erb'
4
5
  require_relative 'args'
5
6
  require_relative 'database_config'
6
7
  require_relative 'deep'
@@ -1662,18 +1662,20 @@
1662
1662
  return 0;
1663
1663
  },
1664
1664
  reconfigure: (state, oldState) => {
1665
- let newVal = getter(state);
1666
- let oldAddr = oldState.config.address[id];
1665
+ let newVal, oldAddr = oldState.config.address[id];
1667
1666
  if (oldAddr != null) {
1668
1667
  let oldVal = getAddr(oldState, oldAddr);
1669
1668
  if (this.dependencies.every(dep => {
1670
1669
  return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) :
1671
1670
  dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true;
1672
- }) || (multi ? compareArray(newVal, oldVal, compare) : compare(newVal, oldVal))) {
1671
+ }) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) {
1673
1672
  state.values[idx] = oldVal;
1674
1673
  return 0;
1675
1674
  }
1676
1675
  }
1676
+ else {
1677
+ newVal = getter(state);
1678
+ }
1677
1679
  state.values[idx] = newVal;
1678
1680
  return 1 /* SlotStatus.Changed */;
1679
1681
  }
@@ -2807,6 +2809,18 @@
2807
2809
  /**
2808
2810
  Find the values for a given language data field, provided by the
2809
2811
  the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet.
2812
+
2813
+ Examples of language data fields are...
2814
+
2815
+ - [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying
2816
+ comment syntax.
2817
+ - [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override)
2818
+ for providing language-specific completion sources.
2819
+ - [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding
2820
+ characters that should be considered part of words in this
2821
+ language.
2822
+ - [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls
2823
+ bracket closing behavior.
2810
2824
  */
2811
2825
  languageDataAt(name, pos, side = -1) {
2812
2826
  let values = [];
@@ -3311,7 +3325,7 @@
3311
3325
  */
3312
3326
  static eq(oldSets, newSets, from = 0, to) {
3313
3327
  if (to == null)
3314
- to = 1000000000 /* C.Far */;
3328
+ to = 1000000000 /* C.Far */ - 1;
3315
3329
  let a = oldSets.filter(set => !set.isEmpty && newSets.indexOf(set) < 0);
3316
3330
  let b = newSets.filter(set => !set.isEmpty && oldSets.indexOf(set) < 0);
3317
3331
  if (a.length != b.length)
@@ -5019,7 +5033,7 @@
5019
5033
  if (pos > 0 ? i == 0 : i == rects.length - 1 || rect.top < rect.bottom)
5020
5034
  break;
5021
5035
  }
5022
- return flattenRect(rect, this.side > 0);
5036
+ return this.length ? rect : flattenRect(rect, this.side > 0);
5023
5037
  }
5024
5038
  get isEditable() { return false; }
5025
5039
  destroy() {
@@ -5909,6 +5923,9 @@
5909
5923
  const perLineTextDirection = /*@__PURE__*/Facet.define({
5910
5924
  combine: values => values.some(x => x)
5911
5925
  });
5926
+ const nativeSelectionHidden = /*@__PURE__*/Facet.define({
5927
+ combine: values => values.some(x => x)
5928
+ });
5912
5929
  class ScrollTarget {
5913
5930
  constructor(range, y = "nearest", x = "nearest", yMargin = 5, xMargin = 5) {
5914
5931
  this.range = range;
@@ -6821,8 +6838,9 @@
6821
6838
  enforceCursorAssoc() {
6822
6839
  if (this.compositionDeco.size)
6823
6840
  return;
6824
- let cursor = this.view.state.selection.main;
6825
- let sel = getSelection$1(this.view.root);
6841
+ let { view } = this, cursor = view.state.selection.main;
6842
+ let sel = getSelection$1(view.root);
6843
+ let { anchorNode, anchorOffset } = view.observer.selectionRange;
6826
6844
  if (!sel || !cursor.empty || !cursor.assoc || !sel.modify)
6827
6845
  return;
6828
6846
  let line = LineView.find(this, cursor.head);
@@ -6837,6 +6855,12 @@
6837
6855
  let dom = this.domAtPos(cursor.head + cursor.assoc);
6838
6856
  sel.collapse(dom.node, dom.offset);
6839
6857
  sel.modify("move", cursor.assoc < 0 ? "forward" : "backward", "lineboundary");
6858
+ // This can go wrong in corner cases like single-character lines,
6859
+ // so check and reset if necessary.
6860
+ view.observer.readSelectionRange();
6861
+ let newRange = view.observer.selectionRange;
6862
+ if (view.docView.posFromDOM(newRange.anchorNode, newRange.anchorOffset) != cursor.from)
6863
+ sel.collapse(anchorNode, anchorOffset);
6840
6864
  }
6841
6865
  mayControlSelection() {
6842
6866
  let active = this.view.root.activeElement;
@@ -8170,9 +8194,9 @@
8170
8194
 
8171
8195
  const wrappingWhiteSpace = ["pre-wrap", "normal", "pre-line", "break-spaces"];
8172
8196
  class HeightOracle {
8173
- constructor() {
8197
+ constructor(lineWrapping) {
8198
+ this.lineWrapping = lineWrapping;
8174
8199
  this.doc = Text.empty;
8175
- this.lineWrapping = false;
8176
8200
  this.heightSamples = {};
8177
8201
  this.lineHeight = 14;
8178
8202
  this.charWidth = 7;
@@ -8911,7 +8935,6 @@
8911
8935
  this.contentDOMHeight = 0;
8912
8936
  this.editorHeight = 0;
8913
8937
  this.editorWidth = 0;
8914
- this.heightOracle = new HeightOracle;
8915
8938
  // See VP.MaxDOMHeight
8916
8939
  this.scaler = IdScaler;
8917
8940
  this.scrollTarget = null;
@@ -8920,7 +8943,7 @@
8920
8943
  // Flag set when editor content was redrawn, so that the next
8921
8944
  // measure stage knows it must read DOM layout
8922
8945
  this.mustMeasureContent = true;
8923
- this.defaultTextDirection = Direction.RTL;
8946
+ this.defaultTextDirection = Direction.LTR;
8924
8947
  this.visibleRanges = [];
8925
8948
  // Cursor 'assoc' is only significant when the cursor is on a line
8926
8949
  // wrap point, where it must stick to the character that it is
@@ -8931,6 +8954,8 @@
8931
8954
  // boundary and, if so, reset it to make sure it is positioned in
8932
8955
  // the right place.
8933
8956
  this.mustEnforceCursorAssoc = false;
8957
+ let guessWrapping = state.facet(contentAttributes).some(v => typeof v != "function" && v.class == "cm-lineWrapping");
8958
+ this.heightOracle = new HeightOracle(guessWrapping);
8934
8959
  this.stateDeco = state.facet(decorations).filter(d => typeof d != "function");
8935
8960
  this.heightMap = HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle.setDoc(state.doc), [new ChangedRange(0, 0, 0, state.doc.length)]);
8936
8961
  this.viewport = this.getViewport(0, null);
@@ -8985,7 +9010,8 @@
8985
9010
  if (scrollTarget)
8986
9011
  this.scrollTarget = scrollTarget;
8987
9012
  if (!this.mustEnforceCursorAssoc && update.selectionSet && update.view.lineWrapping &&
8988
- update.state.selection.main.empty && update.state.selection.main.assoc)
9013
+ update.state.selection.main.empty && update.state.selection.main.assoc &&
9014
+ !update.state.facet(nativeSelectionHidden))
8989
9015
  this.mustEnforceCursorAssoc = true;
8990
9016
  }
8991
9017
  measure(view) {
@@ -9048,7 +9074,7 @@
9048
9074
  oracle.heightChanged = false;
9049
9075
  for (let vp of this.viewports) {
9050
9076
  let heights = vp.from == this.viewport.from ? lineHeights : view.docView.measureVisibleLineHeights(vp);
9051
- this.heightMap = this.heightMap.updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
9077
+ this.heightMap = (refresh ? HeightMap.empty().applyChanges(this.stateDeco, Text.empty, this.heightOracle, [new ChangedRange(0, 0, 0, view.state.doc.length)]) : this.heightMap).updateHeight(oracle, 0, refresh, new MeasuredHeights(vp.from, heights));
9052
9078
  }
9053
9079
  if (oracle.heightChanged)
9054
9080
  result |= 2 /* UpdateFlag.Height */;
@@ -9614,7 +9640,11 @@
9614
9640
  this.bounds = null;
9615
9641
  this.text = "";
9616
9642
  let { impreciseHead: iHead, impreciseAnchor: iAnchor } = view.docView;
9617
- if (start > -1 && !view.state.readOnly && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
9643
+ if (view.state.readOnly && start > -1) {
9644
+ // Ignore changes when the editor is read-only
9645
+ this.newSel = null;
9646
+ }
9647
+ else if (start > -1 && (this.bounds = view.docView.domBoundsAround(start, end, 0))) {
9618
9648
  let selPoints = iHead || iAnchor ? [] : selectionPoints(view);
9619
9649
  let reader = new DOMReader(selPoints, view.state);
9620
9650
  reader.readRange(this.bounds.startDOM, this.bounds.endDOM);
@@ -9908,7 +9938,8 @@
9908
9938
  this.onScroll = this.onScroll.bind(this);
9909
9939
  if (typeof ResizeObserver == "function") {
9910
9940
  this.resize = new ResizeObserver(() => {
9911
- if (this.view.docView.lastUpdate < Date.now() - 75)
9941
+ var _a;
9942
+ if (((_a = this.view.docView) === null || _a === void 0 ? void 0 : _a.lastUpdate) < Date.now() - 75)
9912
9943
  this.onResize();
9913
9944
  });
9914
9945
  this.resize.observe(view.scrollDOM);
@@ -11400,7 +11431,7 @@
11400
11431
  if (scopeObj) {
11401
11432
  if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)]))
11402
11433
  return true;
11403
- if (isChar && (event.shiftKey || event.altKey || event.metaKey || charCode > 127) &&
11434
+ if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
11404
11435
  (baseName = base[event.keyCode]) && baseName != name) {
11405
11436
  if (runFor(scopeObj[prefix + modifiers(baseName, event, true)]))
11406
11437
  return true;
@@ -11452,7 +11483,8 @@
11452
11483
  return [
11453
11484
  selectionConfig.of(config),
11454
11485
  drawSelectionPlugin,
11455
- hideNativeSelection
11486
+ hideNativeSelection,
11487
+ nativeSelectionHidden.of(true)
11456
11488
  ];
11457
11489
  }
11458
11490
  class Piece {
@@ -12210,6 +12242,9 @@
12210
12242
  keyup(e) {
12211
12243
  if (e.keyCode == code || !getter(e))
12212
12244
  this.set(false);
12245
+ },
12246
+ mousemove(e) {
12247
+ this.set(getter(e));
12213
12248
  }
12214
12249
  }
12215
12250
  });
@@ -12444,7 +12479,7 @@
12444
12479
  dom.classList.toggle("cm-tooltip-above", above);
12445
12480
  dom.classList.toggle("cm-tooltip-below", !above);
12446
12481
  if (tView.positioned)
12447
- tView.positioned();
12482
+ tView.positioned(measured.space);
12448
12483
  }
12449
12484
  }
12450
12485
  maybeMeasure() {
@@ -12560,10 +12595,10 @@
12560
12595
  }
12561
12596
  this.mounted = true;
12562
12597
  }
12563
- positioned() {
12598
+ positioned(space) {
12564
12599
  for (let hostedView of this.manager.tooltipViews) {
12565
12600
  if (hostedView.positioned)
12566
- hostedView.positioned();
12601
+ hostedView.positioned(space);
12567
12602
  }
12568
12603
  }
12569
12604
  update(update) {
@@ -12660,10 +12695,10 @@
12660
12695
  }
12661
12696
  }
12662
12697
  }
12663
- mouseleave() {
12698
+ mouseleave(e) {
12664
12699
  clearTimeout(this.hoverTimeout);
12665
12700
  this.hoverTimeout = -1;
12666
- if (this.active)
12701
+ if (this.active && !isInTooltip(e.relatedTarget))
12667
12702
  this.view.dispatch({ effects: this.setHover.of(null) });
12668
12703
  }
12669
12704
  destroy() {
@@ -15988,8 +16023,10 @@
15988
16023
  Facet that defines a way to provide a function that computes the
15989
16024
  appropriate indentation depth, as a column number (see
15990
16025
  [`indentString`](https://codemirror.net/6/docs/ref/#language.indentString)), at the start of a given
15991
- line, or `null` to indicate no appropriate indentation could be
15992
- determined.
16026
+ line. A return value of `null` indicates no indentation can be
16027
+ determined, and the line should inherit the indentation of the one
16028
+ above it. A return value of `undefined` defers to the next indent
16029
+ service.
15993
16030
  */
15994
16031
  const indentService = /*@__PURE__*/Facet.define();
15995
16032
  /**
@@ -16047,7 +16084,7 @@
16047
16084
  context = new IndentContext(context);
16048
16085
  for (let service of context.state.facet(indentService)) {
16049
16086
  let result = service(context, pos);
16050
- if (result != null)
16087
+ if (result !== undefined)
16051
16088
  return result;
16052
16089
  }
16053
16090
  let tree = syntaxTree(context.state);
@@ -16355,7 +16392,7 @@
16355
16392
  let tree = syntaxTree(state);
16356
16393
  if (tree.length < end)
16357
16394
  return null;
16358
- let inner = tree.resolveInner(end);
16395
+ let inner = tree.resolveInner(end, 1);
16359
16396
  let found = null;
16360
16397
  for (let cur = inner; cur; cur = cur.parent) {
16361
16398
  if (cur.to <= end || cur.from > end)
@@ -19520,14 +19557,14 @@
19520
19557
  crelt("br"),
19521
19558
  this.replaceField,
19522
19559
  button("replace", () => replaceNext(view), [phrase(view, "replace")]),
19523
- button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")]),
19524
- crelt("button", {
19525
- name: "close",
19526
- onclick: () => closeSearchPanel(view),
19527
- "aria-label": phrase(view, "close"),
19528
- type: "button"
19529
- }, ["×"])
19530
- ]
19560
+ button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
19561
+ ],
19562
+ crelt("button", {
19563
+ name: "close",
19564
+ onclick: () => closeSearchPanel(view),
19565
+ "aria-label": phrase(view, "close"),
19566
+ type: "button"
19567
+ }, ["×"])
19531
19568
  ]);
19532
19569
  }
19533
19570
  commit() {
@@ -19767,6 +19804,11 @@
19767
19804
  return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : (expr.ignoreCase ? "i" : ""));
19768
19805
  }
19769
19806
  /**
19807
+ This annotation is added to transactions that are produced by
19808
+ picking a completion.
19809
+ */
19810
+ const pickedCompletion = /*@__PURE__*/Annotation.define();
19811
+ /**
19770
19812
  Helper function that returns a transaction spec which inserts a
19771
19813
  completion's text in the main selection range, and any other
19772
19814
  selection range that has the same text in front of it.
@@ -19792,7 +19834,7 @@
19792
19834
  const apply = option.completion.apply || option.completion.label;
19793
19835
  let result = option.source;
19794
19836
  if (typeof apply == "string")
19795
- view.dispatch(insertCompletionText(view.state, apply, result.from, result.to));
19837
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
19796
19838
  else
19797
19839
  apply(view, option.completion, result.from, result.to);
19798
19840
  }
@@ -20027,6 +20069,7 @@
20027
20069
  write: (pos) => this.positionInfo(pos),
20028
20070
  key: this
20029
20071
  };
20072
+ this.space = null;
20030
20073
  let cState = view.state.field(stateField);
20031
20074
  let { options, selected } = cState.open;
20032
20075
  let config = view.state.facet(completionConfig);
@@ -20052,10 +20095,17 @@
20052
20095
  }
20053
20096
  mount() { this.updateSel(); }
20054
20097
  update(update) {
20055
- if (update.state.field(this.stateField) != update.startState.field(this.stateField))
20098
+ var _a, _b, _c;
20099
+ let cState = update.state.field(this.stateField);
20100
+ let prevState = update.startState.field(this.stateField);
20101
+ if (cState != prevState) {
20056
20102
  this.updateSel();
20103
+ if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled))
20104
+ this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled));
20105
+ }
20057
20106
  }
20058
- positioned() {
20107
+ positioned(space) {
20108
+ this.space = space;
20059
20109
  if (this.info)
20060
20110
  this.view.requestMeasure(this.placeInfo);
20061
20111
  }
@@ -20122,27 +20172,32 @@
20122
20172
  let sel = this.dom.querySelector("[aria-selected]");
20123
20173
  if (!sel || !this.info)
20124
20174
  return null;
20125
- let win = this.dom.ownerDocument.defaultView || window;
20126
20175
  let listRect = this.dom.getBoundingClientRect();
20127
20176
  let infoRect = this.info.getBoundingClientRect();
20128
20177
  let selRect = sel.getBoundingClientRect();
20129
- if (selRect.top > Math.min(win.innerHeight, listRect.bottom) - 10 || selRect.bottom < Math.max(0, listRect.top) + 10)
20178
+ let space = this.space;
20179
+ if (!space) {
20180
+ let win = this.dom.ownerDocument.defaultView || window;
20181
+ space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
20182
+ }
20183
+ if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 ||
20184
+ selRect.bottom < Math.max(space.top, listRect.top) + 10)
20130
20185
  return null;
20131
20186
  let rtl = this.view.textDirection == Direction.RTL, left = rtl, narrow = false, maxWidth;
20132
20187
  let top = "", bottom = "";
20133
- let spaceLeft = listRect.left, spaceRight = win.innerWidth - listRect.right;
20188
+ let spaceLeft = listRect.left - space.left, spaceRight = space.right - listRect.right;
20134
20189
  if (left && spaceLeft < Math.min(infoRect.width, spaceRight))
20135
20190
  left = false;
20136
20191
  else if (!left && spaceRight < Math.min(infoRect.width, spaceLeft))
20137
20192
  left = true;
20138
20193
  if (infoRect.width <= (left ? spaceLeft : spaceRight)) {
20139
- top = (Math.max(0, Math.min(selRect.top, win.innerHeight - infoRect.height)) - listRect.top) + "px";
20194
+ top = (Math.max(space.top, Math.min(selRect.top, space.bottom - infoRect.height)) - listRect.top) + "px";
20140
20195
  maxWidth = Math.min(400 /* Info.Width */, left ? spaceLeft : spaceRight) + "px";
20141
20196
  }
20142
20197
  else {
20143
20198
  narrow = true;
20144
- maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : win.innerWidth - listRect.left) - 30 /* Info.Margin */) + "px";
20145
- let spaceBelow = win.innerHeight - listRect.bottom;
20199
+ maxWidth = Math.min(400 /* Info.Width */, (rtl ? listRect.right : space.right - listRect.left) - 30 /* Info.Margin */) + "px";
20200
+ let spaceBelow = space.bottom - listRect.bottom;
20146
20201
  if (spaceBelow >= infoRect.height || spaceBelow > listRect.top) // Below the completion
20147
20202
  top = (selRect.bottom - listRect.top) + "px";
20148
20203
  else // Above it
@@ -20251,21 +20306,24 @@
20251
20306
  return result;
20252
20307
  }
20253
20308
  class CompletionDialog {
20254
- constructor(options, attrs, tooltip, timestamp, selected) {
20309
+ constructor(options, attrs, tooltip, timestamp, selected, disabled) {
20255
20310
  this.options = options;
20256
20311
  this.attrs = attrs;
20257
20312
  this.tooltip = tooltip;
20258
20313
  this.timestamp = timestamp;
20259
20314
  this.selected = selected;
20315
+ this.disabled = disabled;
20260
20316
  }
20261
20317
  setSelected(selected, id) {
20262
20318
  return selected == this.selected || selected >= this.options.length ? this
20263
- : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected);
20319
+ : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
20264
20320
  }
20265
20321
  static build(active, state, id, prev, conf) {
20266
20322
  let options = sortOptions(active, state);
20267
- if (!options.length)
20268
- return null;
20323
+ if (!options.length) {
20324
+ return prev && active.some(a => a.state == 1 /* State.Pending */) ?
20325
+ new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null;
20326
+ }
20269
20327
  let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1;
20270
20328
  if (prev && prev.selected != selected && prev.selected != -1) {
20271
20329
  let selectedValue = prev.options[prev.selected].completion;
@@ -20279,10 +20337,10 @@
20279
20337
  pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8),
20280
20338
  create: completionTooltip(completionState),
20281
20339
  above: conf.aboveCursor,
20282
- }, prev ? prev.timestamp : Date.now(), selected);
20340
+ }, prev ? prev.timestamp : Date.now(), selected, false);
20283
20341
  }
20284
20342
  map(changes) {
20285
- return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected);
20343
+ return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled);
20286
20344
  }
20287
20345
  }
20288
20346
  class CompletionState {
@@ -20305,9 +20363,12 @@
20305
20363
  });
20306
20364
  if (active.length == this.active.length && active.every((a, i) => a == this.active[i]))
20307
20365
  active = this.active;
20308
- let open = tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
20309
- !sameResults(active, this.active) ? CompletionDialog.build(active, state, this.id, this.open, conf)
20310
- : this.open && tr.docChanged ? this.open.map(tr.changes) : this.open;
20366
+ let open = this.open;
20367
+ if (tr.selection || active.some(a => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) ||
20368
+ !sameResults(active, this.active))
20369
+ open = CompletionDialog.build(active, state, this.id, this.open, conf);
20370
+ else if (open && tr.docChanged)
20371
+ open = open.map(tr.changes);
20311
20372
  if (!open && active.every(a => a.state != 1 /* State.Pending */) && active.some(a => a.hasResult()))
20312
20373
  active = active.map(a => a.hasResult() ? new ActiveSource(a.source, 0 /* State.Inactive */) : a);
20313
20374
  for (let effect of tr.effects)
@@ -20447,7 +20508,7 @@
20447
20508
  function moveCompletionSelection(forward, by = "option") {
20448
20509
  return (view) => {
20449
20510
  let cState = view.state.field(completionState, false);
20450
- if (!cState || !cState.open ||
20511
+ if (!cState || !cState.open || cState.open.disabled ||
20451
20512
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
20452
20513
  return false;
20453
20514
  let step = 1, tooltip;
@@ -20472,7 +20533,8 @@
20472
20533
  if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 ||
20473
20534
  Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay)
20474
20535
  return false;
20475
- applyCompletion(view, cState.open.options[cState.open.selected]);
20536
+ if (!cState.open.disabled)
20537
+ applyCompletion(view, cState.open.options[cState.open.selected]);
20476
20538
  return true;
20477
20539
  };
20478
20540
  /**
@@ -20677,10 +20739,16 @@
20677
20739
  background: "#17c",
20678
20740
  color: "white",
20679
20741
  },
20742
+ "&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
20743
+ background: "#777",
20744
+ },
20680
20745
  "&dark .cm-tooltip-autocomplete ul li[aria-selected]": {
20681
20746
  background: "#347",
20682
20747
  color: "white",
20683
20748
  },
20749
+ "&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": {
20750
+ background: "#444",
20751
+ },
20684
20752
  ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": {
20685
20753
  content: '"···"',
20686
20754
  opacity: 0.5,
@@ -22355,7 +22423,7 @@
22355
22423
  }
22356
22424
  let next = input.next, low = 0, high = data[state + 2];
22357
22425
  // Special case for EOF
22358
- if (input.next < 0 && high > low && data[accEnd + high * 3 - 3] == 65535 /* End */) {
22426
+ if (input.next < 0 && high > low && data[accEnd + high * 3 - 3] == 65535 /* End */ && data[accEnd + high * 3 - 3] == 65535 /* End */) {
22359
22427
  state = data[accEnd + high * 3 - 1];
22360
22428
  continue scan;
22361
22429
  }
@@ -22363,7 +22431,7 @@
22363
22431
  for (; low < high;) {
22364
22432
  let mid = (low + high) >> 1;
22365
22433
  let index = accEnd + mid + (mid << 1);
22366
- let from = data[index], to = data[index + 1];
22434
+ let from = data[index], to = data[index + 1] || 0x10000;
22367
22435
  if (next < from)
22368
22436
  high = mid;
22369
22437
  else if (next >= to)
@@ -23334,7 +23402,7 @@
23334
23402
  spaceAfterDashes: false,
23335
23403
  slashComments: false,
23336
23404
  doubleQuotedStrings: false,
23337
- doubleDollarStrings: false,
23405
+ doubleDollarQuotedStrings: false,
23338
23406
  unquotedBitLiterals: false,
23339
23407
  treatBitsAsBytes: false,
23340
23408
  charSetCasts: false,
@@ -23361,7 +23429,7 @@
23361
23429
  input.advance();
23362
23430
  input.acceptToken(whitespace);
23363
23431
  }
23364
- else if (next == 36 /* Ch.Dollar */ && input.next == 36 /* Ch.Dollar */ && d.doubleDollarStrings) {
23432
+ else if (next == 36 /* Ch.Dollar */ && input.next == 36 /* Ch.Dollar */ && d.doubleDollarQuotedStrings) {
23365
23433
  readDoubleDollarLiteral(input);
23366
23434
  input.acceptToken(String$1);
23367
23435
  }
@@ -23745,6 +23813,7 @@
23745
23813
  static define(spec) {
23746
23814
  let d = dialect(spec, spec.keywords, spec.types, spec.builtin);
23747
23815
  let language = LRLanguage.define({
23816
+ name: "sql",
23748
23817
  parser: parser.configure({
23749
23818
  tokenizers: [{ from: tokens, to: tokensFor(d) }]
23750
23819
  }),
@@ -23764,14 +23833,6 @@
23764
23833
  return completeKeywords(dialect.dialect.words, upperCase);
23765
23834
  }
23766
23835
  /**
23767
- FIXME remove on 1.0 @internal
23768
- */
23769
- function keywordCompletion(dialect, upperCase = false) {
23770
- return dialect.language.data.of({
23771
- autocomplete: keywordCompletionSource(dialect, upperCase)
23772
- });
23773
- }
23774
- /**
23775
23836
  Returns a completion sources that provides schema-based completion
23776
23837
  for the given configuration.
23777
23838
  */
@@ -23779,27 +23840,6 @@
23779
23840
  return config.schema ? completeFromSchema(config.schema, config.tables, config.defaultTable, config.defaultSchema)
23780
23841
  : () => null;
23781
23842
  }
23782
- /**
23783
- FIXME remove on 1.0 @internal
23784
- */
23785
- function schemaCompletion(config) {
23786
- return config.schema ? (config.dialect || StandardSQL).language.data.of({
23787
- autocomplete: schemaCompletionSource(config)
23788
- }) : [];
23789
- }
23790
- /**
23791
- SQL language support for the given SQL dialect, with keyword
23792
- completion, and, if provided, schema-based completion as extra
23793
- extensions.
23794
- */
23795
- function sql(config = {}) {
23796
- let lang = config.dialect || StandardSQL;
23797
- return new LanguageSupport(lang.language, [schemaCompletion(config), keywordCompletion(lang, !!config.upperCaseKeywords)]);
23798
- }
23799
- /**
23800
- The standard SQL dialect.
23801
- */
23802
- const StandardSQL = /*@__PURE__*/SQLDialect.define({});
23803
23843
  const MySQLKeywords = "accessible algorithm analyze asensitive authors auto_increment autocommit avg avg_row_length binlog btree cache catalog_name chain change changed checkpoint checksum class_origin client_statistics coalesce code collations columns comment committed completion concurrent consistent contains contributors convert database databases day_hour day_microsecond day_minute day_second delay_key_write delayed delimiter des_key_file dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile enable enclosed ends engine engines enum errors escaped even event events every explain extended fast field fields flush force found_rows fulltext grants handler hash high_priority hosts hour_microsecond hour_minute hour_second ignore ignore_server_ids import index index_statistics infile innodb insensitive insert_method install invoker iterate keys kill linear lines list load lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modify mutex mysql_errno no_write_to_binlog offline offset one online optimize optionally outfile pack_keys parser partition partitions password phase plugin plugins prev processlist profile profiles purge query quick range read_write rebuild recover regexp relaylog remove rename reorganize repair repeatable replace require resume rlike row_format rtree schedule schema_name schemas second_microsecond security sensitive separator serializable server share show slave slow snapshot soname spatial sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result ssl starting starts std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace terminated triggers truncate uncommitted uninstall unlock upgrade use use_frm user_resources user_statistics utc_date utc_time utc_timestamp variables views warnings xa xor year_month zerofill";
23804
23844
  const MySQLTypes = SQLTypes + "bool blob long longblob longtext medium mediumblob mediumint mediumtext tinyblob tinyint tinytext text bigint int1 int2 int3 int4 int8 float4 float8 varbinary varcharacter precision datetime unsigned signed";
23805
23845
  const MySQLBuiltin = "charset clear edit ego help nopager notee nowarning pager print prompt quit rehash source status system tee";
@@ -23869,7 +23909,6 @@
23869
23909
  }
23870
23910
  const type = 'text/plain';
23871
23911
  let text = window.sqlFetch.result.columns.map((header) => {
23872
- console.log(header.includes(delimiter));
23873
23912
  if (typeof header === 'string' && (header.includes('"') || header.includes(delimiter))) {
23874
23913
  return `"${header.replaceAll('"', '""')}"`
23875
23914
  } else {
@@ -23912,10 +23951,35 @@
23912
23951
  });
23913
23952
  const schemas = Object.entries(window.metadata.schemas);
23914
23953
  const editorSchema = {};
23954
+ const tables = [];
23955
+ const hasTableAliases = Object.keys(window.metadata.table_aliases).length > 0;
23915
23956
  schemas.forEach(([schemaName, schema]) => {
23916
23957
  Object.entries(schema.tables).forEach(([tableName, table]) => {
23917
23958
  const qualifiedTableName = schemas.length === 1 ? tableName : `${schemaName}.${tableName}`;
23918
- editorSchema[qualifiedTableName] = Object.keys(table.columns);
23959
+ const columns = Object.keys(table.columns);
23960
+ editorSchema[qualifiedTableName] = columns;
23961
+ const alias = window.metadata.table_aliases[tableName];
23962
+ if (alias) {
23963
+ editorSchema[alias] = columns;
23964
+ tables.push({
23965
+ label: qualifiedTableName,
23966
+ detail: alias,
23967
+ alias_type: 'with',
23968
+ quoted: '`' + qualifiedTableName + '` ' + alias,
23969
+ unquoted: `${qualifiedTableName} ${alias}`
23970
+ });
23971
+ tables.push({
23972
+ label: qualifiedTableName,
23973
+ detail: alias,
23974
+ alias_type: 'only',
23975
+ quoted: '`' + alias + '`',
23976
+ unquoted: alias
23977
+ });
23978
+ } else {
23979
+ tables.push({
23980
+ label: qualifiedTableName
23981
+ });
23982
+ }
23919
23983
  });
23920
23984
  });
23921
23985
  // I prefer to use Cmd-Enter/Ctrl-Enter to submit the query. Here I am replacing the default mapping.
@@ -23948,6 +24012,80 @@
23948
24012
  ...completionKeymap,
23949
24013
  ...lintKeymap
23950
24014
  ]);
24015
+ const sqlConfig = {
24016
+ dialect: MySQL,
24017
+ upperCaseKeywords: true,
24018
+ schema: editorSchema,
24019
+ tables
24020
+ };
24021
+ const scs = schemaCompletionSource(sqlConfig);
24022
+ const sqlExtension = new LanguageSupport(
24023
+ MySQL.language,
24024
+ [
24025
+ MySQL.language.data.of({
24026
+ autocomplete: (context) => {
24027
+ const result = scs(context);
24028
+ if (!hasTableAliases || !result?.options) return result
24029
+
24030
+ const tree = syntaxTree(context.state);
24031
+ let node = tree.resolveInner(context.pos, -1);
24032
+
24033
+ // We are trying to identify the case where we are autocompleting a table name after "from" or "join"
24034
+ // TODO: we don't handle the case where a user typed "select table.foo from". In that case we probably
24035
+ // shouldn't autocomplete the alias. Though, if the user typed "select table.foo, t.bar", we won't know
24036
+ // what to do.
24037
+ // TODO: if table aliases aren't enabled, we don't need to override autocomplete.
24038
+
24039
+ if (node?.name === 'Statement') {
24040
+ // The node can be a Statement if the cursor is at the end of "from " and there is a complete
24041
+ // statement in the editor (semicolon present). In that case we want to find the node just before the
24042
+ // current position so that we can check whether it is "from" or "join".
24043
+ node = node.childBefore(context.pos);
24044
+ } else if (node?.name === 'Script') {
24045
+ // It seems the node can sometimes be a Script if the cursor is at the end of the last statement in the
24046
+ // editor and the statement doesn't end in a semicolon. In that case we can find the last statement in the
24047
+ // Script so that we can check whether it is "from" or "join".
24048
+ node = node.lastChild?.childBefore(context.pos);
24049
+ } else if (['Identifier', 'QuotedIdentifier', 'Keyword'].includes(node?.name)) {
24050
+ // If the node is an Identifier, we might be in the middle of typing the table name. If the node is a
24051
+ // Keyword but isn't "from" or "join", we might be in the middle of typing a table name that is similar
24052
+ // to a Keyword, for instance "orders" or "selections" or "fromages". In these cases, look for the previous
24053
+ // sibling so that we can check whether it is "from" or "join".
24054
+ node = node.prevSibling;
24055
+ }
24056
+
24057
+ const nodeText = node ? context.state.doc.sliceString(node.from, node.to).toLowerCase() : null;
24058
+ if (node?.name === 'Keyword' && ['from', 'join'].includes(nodeText)) {
24059
+ result.options = result.options.filter((option) => {
24060
+ return option.alias_type === undefined || option.alias_type === 'with'
24061
+ });
24062
+ } else {
24063
+ result.options = result.options.filter((option) => {
24064
+ return option.alias_type === undefined || option.alias_type === 'only'
24065
+ });
24066
+ }
24067
+ result.options = result.options.map((option) => {
24068
+ // Some shenanigans. If the default autocomplete function quoted the label, we want to ensure the quote
24069
+ // only applies to the table name and not the alias. You might think we could do this by overriding the
24070
+ // apply function but apply is set to null when quoting.
24071
+ // See https://github.com/codemirror/lang-sql/blob/ebf115fffdbe07f91465ccbd82868c587f8182bc/src/complete.ts#L90
24072
+ if (option.alias_type) {
24073
+ if (option.label.match(/^`.*`$/)) {
24074
+ option.apply = option.quoted;
24075
+ } else {
24076
+ option.apply = option.unquoted;
24077
+ }
24078
+ }
24079
+ return option
24080
+ });
24081
+ return result
24082
+ }
24083
+ }),
24084
+ MySQL.language.data.of({
24085
+ autocomplete: keywordCompletionSource(MySQL, true)
24086
+ })
24087
+ ]
24088
+ );
23951
24089
  window.editorView = new EditorView({
23952
24090
  state: EditorState.create({
23953
24091
  extensions: [
@@ -23969,11 +24107,7 @@
23969
24107
  highlightActiveLine(),
23970
24108
  highlightSelectionMatches(),
23971
24109
  editorKeymap,
23972
- sql({
23973
- dialect: MySQL,
23974
- upperCaseKeywords: true,
23975
- schema: editorSchema
23976
- }),
24110
+ sqlExtension,
23977
24111
  fixedHeightEditor,
23978
24112
  placeholder('Let\'s query!')
23979
24113
  ]
@@ -24161,6 +24295,12 @@
24161
24295
  while (tablesElement.firstChild) {
24162
24296
  tablesElement.removeChild(tablesElement.firstChild);
24163
24297
  }
24298
+ while (columnsElement.firstChild) {
24299
+ columnsElement.removeChild(columnsElement.firstChild);
24300
+ }
24301
+ while (indexesElement.firstChild) {
24302
+ indexesElement.removeChild(indexesElement.firstChild);
24303
+ }
24164
24304
  const schemaName = schemasElement.value;
24165
24305
  const schema = window.metadata.schemas[schemaName];
24166
24306
  const tableNames = Object.keys(schema.tables);
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.38
4
+ version: 0.1.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-15 00:00:00.000000000 Z
11
+ date: 2022-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mysql2