sqlui 0.1.38 → 0.1.39

Sign up to get free protection for your applications and to get access to all the features.
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